62. Unique Paths

62. Unique Paths

A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below).

The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below).

How many possible unique paths are there?


Above is a 7 x 3 grid. How many possible unique paths are there?

Note: m and n will be at most 100.

Example 1:

Input: m = 3, n = 2
Output: 3
Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -> Right -> Down
2. Right -> Down -> Right
3. Down -> Right -> Right

Example 2:

Input: m = 7, n = 3
Output: 28

  题目要求到达终点的可能路径,在已知长宽的情况下,到达途中Finish,一共要走(m - 1) + ( n - 1) = m + n - 2,其中向右m - 1步,向下n - 1步,很典型的排列组合问题,即在m + n - 2步中选 m - 1个位置向右走,剩下的自然是向下走,得到公式:

    N = C(m + n - 2, m - 1)

  编码完成组合公式的计算:

    public int uniquePaths(int m, int n) {
        return choose(m + n - 2, m > n ? n - 1 : m - 1);
    }

    public static int choose(int m, int n) {
        int member = 1;
        int temp = n;
        while (temp-- > 0) {
            member *= m--;
        }
        int denominator = 1;
        int start = 1;
        temp = n;
        while (temp-- > 0) {
            denominator *= start++;
        }
        return member / denominator;
    }

  提交后发现,在case : m = 10 , n = 10时,未通过。想了半天确定解题思路没问题,于是一步步debug,终于发现乘法计算中,超出了int能表示的最大整数!

  换种写法:

    public static int choose(int m, int n) {
        long result = 1;
        for (int i = 0; i < n; i++) {
            result = result * (m - i) / (i + 1);
        }
        return (int) result;
    }

  注意这里写成:

    result *= (m - i) / (i + 1);

  答案依然会不对,因为:

result *=  (m - i) / (i + 1) 等同于result = (m - i)/(i + 1)*result;

  而答案中的写法其实是发生了自动转型的,即long * int  -> long * long。

  额,上面那段鬼话,差点把自己也骗过去了,其实写这段字的时候我就一直有个疑问,不管是long还是int,都只能表示整数,而除法(m - i)/(i + 1)是明显会产生余数而被近似的,为什么最终却能产生正确答案?

  回过头来看数学里的组合公式(即C(m,n))同样也有这样的问题,物理意义上组合是不存在小数种选择的,但是,这是个带除法的公式,是怎么保证结果一定是整数的?

  这里把公式的计算过程展开就很容易看出原因了,字母不好写,我直接以实际例子做演示:

  例如C(19 ,5),通过公式展开可知道结果为:(19*18*17*16*15)/(1*2*3*4*5),这里如果变成(19/1 ) * (18/2) *(17/3) *(16/4) * (15/5) 即是代码中result = (m - i)/(i + 1)*result的运算方式,明显可以看到(17/3)是无法整除的。但是,单独观察19/1 ) * (18/2) *(17/3) 这部分,运算开始时19/1,一定是整数,当18/2的时候,这时分子有19 * 18,分母是1 * 2,其中,2虽然不能被任何数整除,但是按照公式展开的方式

  当我们要除分母中的n时,分子此时一定可以写成n个连续的数相乘,既然分子已经是n个连续数相乘(循环了一个n周期),那么这n个数中一定存在一个数可以整除分母中的n。

  在上面的例子里17/3虽然不能整除,但是19*18*17/(1*2*3),3个数中必然存在一个数(这里是18)可以除尽3。

  这才是为什么代码里必须写成result = result * (m - i) / (i + 1),而不是先除后乘的形式result = (m - i)/(i + 1)*result !!!

 

  当然这题目其实也可以用dp,从finish点向前推一步,可以得到:

    1、机器人在向右一步才到达终点,即dp[m-1][n] -> right ;

    2、机器人向下一步才到达终点,即dp[m][n-1] -> down。

  由上容易知道dp方程为 : dp[m][n] = dp[m-1][n] + dp[m][n-1] ,另外dp[1[n] = dp[m][1] = 1。

    public static int uniquePaths2(int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (i == 1 || j == 1) {
                    dp[i][j] = 1;
                } else {
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                }
            }
        }
        return dp[m][n];
    }
原文地址:https://www.cnblogs.com/lyInfo/p/9126230.html