ProjectEuler#15遇到问题

时间:2014-10-17 04:00:54

标签: java math

问题在于:https://projecteuler.net/problem=15

我想出了一个我认为可以为此工作的模式,我已经看过其他人做了什么,他们做了同样的事情,比如这里:{{ 3}}但我总是得到一个不同的答案。这是我的代码。

import java.util.*;
public class Problem15 {
    public static void main(String[] args) {
        ArrayList<Long> list = new ArrayList<Long>();
        list.add((long)1);
        int size;
        for (int x = 1;x<20;x++){
            size = list.size();
            for(int y = 1;y<size;y++){
                    long sum = list.get(y-1)+list.get(y);
                    list.set(y, sum);
            }
        list.add(list.get(size-1)*2);
        System.out.println(list);
        }
    }
}

编辑: 为了回应爱德华,我认为我的方法是你在编辑之前所说的,因为这不是关于蛮力的,但我只是从网格中的每个点总结可能的方法。但是,我不需要一个二维阵列来做这个,因为我只是从侧面看可能的移动。这是我编写的内容,希望能够解释我的过程。 My Method

所以对于1x1。就像你说的那样,一旦达到一个方向的极限,你只能在另一个方向的极限上旅行,所以有两种方式。这对于1x1并不是特别有用,但对于较大的1x1则是如此​​。对于2x2,您知道作为右边界限的顶角只有1个可能的路径。同样的逻辑适用于底角。并且,因为你有一个已经解决过的方形,1x1,你知道中间点有2条路径。现在,如果你看两边,你会看到例如在它下面有2而在右边有1的点有这些相邻点中的路径数的总和,所以那个点必须有3个路径。对于另一方也是如此,左上角的总和为3和3,或者是2倍。

现在,如果您查看我的代码,那就是它正在尝试做的事情。索引为0的元素始终为1,对于数组的其余部分,它将前一个术语与其自身相加,并替换当前术语。最后,为了找到路径总数,它只是最后一个数字的两倍。因此,如果程序尝试解决4x4,则该阵列目前看起来像{1,4,10,20}。因此程序会将其更改为{1,5,10,20},然后{1,5,15,20},然后{1,5,15,35},最后添加路径总数{{ 1,5,15,35,70}。我认为这是你在答案中向我解释的内容,但我的答案总是错误的。

2 个答案:

答案 0 :(得分:5)

意识到数学复杂性比蛮力搜索更重要。

您有一个二维点阵列,您可以选择仅在x或y方向远离原点。因此,您可以像这样代表您的旅行:

(0, 0), (1, 0), (1, 1), (2, 1), (2, 2)
有些事情变得很明显。第一个是通过混乱的任何路径将需要x + y步,穿过x + y + 1个位置。这是曼哈顿距离风格路径的一个特征。

第二个是在任何一个点,直到你达到最大x或y,你可以选择两个选项之一(x或y);但是,只要一个或另一个达到它的极限,剩下的唯一选择是重复选择非最大值,直到它也变为最大值。

有了这个,你可能有足够的提示来解决数学问题。然后,您甚至不需要搜索不同的路径来获得可以解决问题的算法。

---编辑以提供更多提示---

每个二维路径数组可以分解为更小的二维路径数组。因此,函数f(3, 5)产生路径数的f解决方案等于f(2, 5) + f(3, 4)。请注意,f(0, 5)直接等于1f(3, 0)也是如此,因为当路径被强制为线性时,您不再有“选择”。

对函数建模后,您甚至不需要数组来遍历路径......

f(1, 1) = f(0, 1) + f(1, 0)
f(0, 1) = 1
f(1, 0) = 1
f(1, 1) = 1 + 1
f(1, 1) = 2

和一组3 x 3顶点(如引用的例子)

f(2, 2) = f(1, 2) + f(2, 1)
f(1, 2) = f(0, 1) + f(1, 1)

(从之前)

f(1, 1) = 2
f(0, 2) = 1
f(1, 2) = 2 + 1 = 3

同样(因为它是镜像)

f(2, 1) = 1 + 2 = 3

所以

f(2, 2) = 3 + 3 = 6

---最后编辑(我希望!)---

好的,现在你可能会认为你有两个选择(向下)或(向右)。考虑一个包含四个“命令”的包,2个“向下”和2个“向右”。您可以从包中选择多少种不同的方式?

这样的“选择”是一种排列,但由于我们选择了所有这些,它是一种特殊的排列类型,称为“顺序”或“排序”。

二项式(一个或另一个)排序的数量由数学公式

决定

订单数量= (A + B)!/(A! * B!)

其中AA类型的项目的“计数”,而BB类型的项目的“计数”

3x3顶点,2个向下选择,2个正确选择

订购数量=(2 + 2)!/ 2!* 2!

4!/1*2*1*2
1*2*3*4/1*2*1*2
(1*2)*3*4/(1*2)*1*2
3*4/2
12/2
6

如果需要的话,你可以手动做20 * 20,但是阶乘公式很容易通过计算机完成(虽然请注意你不要用整数溢出破坏答案)。

答案 1 :(得分:0)

另一种实施方式:

public static void main(String[] args) {
    int n = 20;
    long matrix[][] = new long[n][n];
    for (int i = 0; i < n; i++) {
        matrix[i][0] = i + 2;
        matrix[0][i] = i + 2;
    }
    for (int i = 1; i < n; i++) {
        for (int j = i; j < n; j++) {      // j>=i
            matrix[i][j] = matrix[i - 1][j] + matrix[i][j - 1];
            matrix[j][i] = matrix[i][j];   // avoids double computation (difference)
        }
    }
    System.out.println(matrix[n - 1][n - 1]);
}

时间:43微秒(不打印)

它基于以下矩阵:

   | 1  2  3  4 ...
---------------------
 1 | 2  3  4  5 ...
 2 | 3  6  10 15
 3 | 4  10 20 35
 4 | 5  15 35 70
 . | .
 . | .
 . | .

,其中

6  =  3 +  3
10 =  6 +  4
15 = 10 +  5
...
70 = 35 + 35

请注意,我在实现中使用了i + 2而不是i + 1,因为第一个索引是0


当然,最快的解决方案是使用数学公式(参见Edwin的帖子)及其代码:

public static void main(String[] args) {
    int n = 20;
    long result = 1;
    for ( int i = 1 ; i <= n ; i++ ) {
        result *= (i+n);
        result /= i;
    }
    System.out.println(result);
}

只需 5微秒(不打印)。

如果您担心精度会下降,请注意n个连续数字的乘积可以被n!整除。

要更好地理解为什么公式是:

     (d+r)!
F = ---------  , where |D| = d and |R| = r
      d!*r!

而不是F = (d+r)!,假设每个“向下”和“向右”都有一个索引:

down1,right1,right2,down2,down3,right3

第二个公式计算上面“命令”的所有可能排列,但在我们的例子中,down1,down2和down3之间没有区别。因此,第二个公式会将63!)计为同一个数字:

down1,down2,down3
down1,down3,down2
down2,down1,down3
down2,down3,down1
down3,down1,down2
down3,down2,down1

这就是我们将(d+r)!除以d!的原因。类似于r!