409,动态规划求不同路径
Keep on going never give up!
勇往直前,决不放弃
问题描述
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3
输出: 28
提示:
1 <= m, n <= 100
题目数据保证答案小于等于 2 * 10 ^ 9
动态规划解决
注意这里机器人只能向下和向右移动,不能往其他方向移动,我们用dp[i][j]表示到坐标(i,j)这个格内有多少条不同的路径,所以最终的答案就是求dp[m-1][n-1]。
因为只能从上面或左边走过来,所以递推公式是
dp[i][j]=dp[i-1][j]+dp[i][j-1]。
dp[i-1][j]表示的是从上面走过来的路径条数。
dp[i][j-1]表示的是从左边走过来的路径条数。
那么边界条件是什么呢,如果Finish在第一行的任何位置都只有一条路径,同理Finish在第一列的任何位置也都只有一条路径,所以边界条件是第一行和第一列都是1。我们已经找到了递推公式,又找到了边界条件,所以动态规划代码很容易写出来,我们来看下
1public int uniquePaths(int m, int n) {
2 int[][] dp = new int[m][n];
3 //第一列都是1
4 for (int i = 0; i < m; i++) {
5 dp[i][0] = 1;
6 }
7 //第一行都是1
8 for (int i = 0; i < n; i++) {
9 dp[0][i] = 1;
10 }
11
12 //这里是递推公式
13 for (int i = 1; i < m; i++)
14 for (int j = 1; j < n; j++)
15 dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
16 return dp[m - 1][n - 1];
17}
18
动态规划优化
我们看上面二维数组的递推公式,当前坐标的值只和左边与上面的值有关,和其他的无关,这样二维数组造成大量的空间浪费,所以我们可以把它改为一维数组。
1public int uniquePaths(int m, int n) {
2 int[] dp = new int[m];
3 Arrays.fill(dp, 1);
4 for (int j = 1; j < n; j++)
5 for (int i = 1; i < m; i++)
6 dp[i] += dp[i - 1];
7 return dp[m - 1];
8}
这里的dp[i]+=dp[i-1];实际上就是dp[i]=dp[i]+dp[i-1],我们可以这样理解,上面的网格我们是一行一行计算的,dp[i]也就是上面红色的表示的是当前位置上面的值,dp[i-1]表示的是当前位置左边的值。
递归方式
这题除了动态规划以外,还可以把上面的动态规划改为递归的方式
1public int uniquePaths(int m, int n) {
2 return uniquePathsHelper(1, 1, m, n);
3}
4
5//第i行第j列到第m行第n列共有多少种路径
6public int uniquePathsHelper(int i, int j, int m, int n) {
7 //边界条件的判断
8 if (i > m || j > n)
9 return 0;
10 if ((i == m && j == n))
11 return 1;
12 //从右边走有多少条路径
13 int right = uniquePathsHelper(i + 1, j, m, n);
14 //从下边走有多少条路径
15 int down = uniquePathsHelper(i, j + 1, m, n);
16 //返回总的路径
17 return right + down;
18}
代码中有注释,很容易理解,但其实这种效率很差,因为他包含了大量的重复计算,我们画个图来看一下。
我们看到上面图中红色,黑色,还有那种什么颜色的都表示重复的计算,所以有一种方式就是把计算过的值使用一个map存储起来,用的时候先查看是否计算过,如果计算过就直接拿来用,看下代码
1public int uniquePaths(int m, int n) {
2 return uniquePathsHelper(1, 1, m, n, new HashMap<>());
3}
4
5public int uniquePathsHelper(int i, int j, int m, int n, Map<String, Integer> map) {
6 if (i > m || j > n)
7 return 0;
8 if ((i == m && j == n))
9 return 1;
10 String key = i + "*" + j;
11 if (map.containsKey(key))
12 return map.get(key);
13 int right = uniquePathsHelper(i + 1, j, m, n, map);
14 int down = uniquePathsHelper(i, j + 1, m, n, map);
15 int totla = right + down;
16 map.put(key, totla);
17 return totla;
18}
使用公式计算
我们要想到达终点,需要往下走n-1步,往右走m-1步,总共需要走n+m-2步。他无论往右走还是往下走他的总的步数是不会变的。也就相当于总共要走n+m-2步,往右走m-1步总共有多少种走法,很明显这就是一个排列组合问题,公式如下
排列组合的计算公式如下
公式为(m+n-2)! / [(m-1)! * (n-1)!]
代码如下
1public int uniquePaths(int m, int n) {
2 int N = n + m - 2;
3 double res = 1;
4 for (int i = 1; i < m; i++)
5 res = res * (N - (m - 1) + i) / i;
6 return (int) res;
7}
总结
这题使用动态规划是最容易理解也是最容易解决的,当然后面的递归和公式计算也能解决。
●
●
●
●
长按上图,识别图中二维码之后即可关注。
如果喜欢这篇文章就点个"赞"吧