https://oj.leetcode.com/problems/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 3 x 7 grid. How many possible unique paths are there?
Note: m and n will be at most 100.
解题思路:
这道题其实是一个高中数学的排列组合题目。将格子视为一个二维数组a[m][n],从a[0][0]到a[m - 1][n - 1]总共需要走m-1+n-1=m+n-2步。其中,必然有n-1步往右,m-1部往下,区别就是哪n-1往右,或哪m-1往下。于是便可以转化为组合的题目,用C可以解决,就是Cm+n-2 m-1,或者Cm+n-2 n-1。因为根据公式,Cmn = Cmm -n.数学公式有Cmn = m!/((m - n)!*n!)。便可以直接求解了。
需要注意的是,如果直接定义int的阶乘的函数,很容易就越界了。这里需要引用java.math.BigInteger类,类似与C++中的long long类型。用它的乘法和除法,还要注意他的valueOf()与intValue()方法。
public class Solution { public static java.math.BigInteger factorial(int n){ java.math.BigInteger result = java.math.BigInteger.valueOf(1); if(n == 0){ return java.math.BigInteger.valueOf(1); } while(n > 1){ result = result.multiply(java.math.BigInteger.valueOf(n)); n--; } return result; } public int uniquePaths(int m, int n) { return (factorial(m + n - 2).divide(factorial(m - 1).multiply(factorial(n - 1)))).intValue(); } }
还可以定义化简后的阶乘,表示从m一直乘到n。
public class Solution { public static java.math.BigInteger factorial(int m, int n){ //m * (m - 1) * ... * (n + 1) java.math.BigInteger result = java.math.BigInteger.valueOf(1); while(m > n){ result = result.multiply(java.math.BigInteger.valueOf(m)); m--; } return result; } public int uniquePaths(int m, int n) { return (factorial(m + n - 2, n - 1).divide(factorial(m - 1, 1))).intValue(); } }
简单点,用double来解题。注意最后用Math.round()方法来四舍五入,否则可能会误差1。注意,这里用float不可以。
public class Solution { public static double factorial(int m, int n){ //m * (m - 1) * ... * (n + 1) double result = 1; while(m > n){ result = result * m; m--; } return result; } public int uniquePaths(int m, int n) { return (int)Math.round(factorial(m + n - 2, n - 1) / factorial(m - 1, 1)); } }
再来一个动态规划的解题思路。我们将子问题,即状态定义为dp[m][n]表示到点a[m][n]的不同路径数量。那么要到点a[m][n],因为每次只能往下或往右,所以只有两种可能,从a[i - 1][j]或者a[i][j - 1]。要注意首先将到达最左侧和最上面的所有值设置为1,因为肯定只有1种可能路径。代码如下,时间复杂度为O(m*n)。
public class Solution { public int uniquePaths(int m, int n) { int[][] dp = new int[m][n]; //dp[m][n]表示到点a[m][n]的不同路径数量 for(int i = 0; i < n; i++){ dp[0][i] = 1; } for(int i = 0; i < m; i++){ dp[i][0] = 1; } for(int i = 1; i < m; i++){ for(int j = 1; j < n; j++){ dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } } return dp[m - 1][n - 1]; } }
上述dp问题实际上是一个一维的dp,可以用一个一维数组便可解决问题,节省空间。因为dp[i][j]等于本列上一行+本行前一列,那么其实只要用一行数据便可,永远存最新(旧)的数据。算第j列时,dp[j] = dp[j - 1] + dp[j];用已经更新了的dp[j-1],即本行前一列的值,去加上未更新的dp[j],即本列上一行的值,算到的新的dp[j]就是新的本行本列的值了。这个思路有些巧妙,需要想通。
public class Solution { public int uniquePaths(int m, int n) { int[] dp = new int[n]; //dp[m][n]表示到点a[m][n]的不同路径数量 //所有元素初始化值为0 dp[0] = 1; for(int i = 0; i < m; i++){ for(int j = 1; j < n; j++){ dp[j] = dp[j - 1] + dp[j]; } } return dp[n - 1]; } }