409,动态规划求不同路径
想了解更多数据结构以及算法题,可以关注微信公众号“数据结构和算法”,每天一题为你精彩解答。也可以扫描下面的二维码关注
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 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。我们已经找到了递推公式,又找到了边界条件,所以动态规划代码很容易写出来,我们来看下
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
//第一列都是1
for (int i = 0; i < m; i++) {
dp[i][0] = 1;
}
//第一行都是1
for (int i = 0; i < n; i++) {
dp[0][i] = 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];
}
动态规划优化
我们看上面二维数组的递推公式,当前坐标的值只和左边与上面的值有关,和其他的无关,这样二维数组造成大量的空间浪费,所以我们可以把它改为一维数组。
public int uniquePaths(int m, int n) {
int[] dp = new int[m];
Arrays.fill(dp, 1);
for (int j = 1; j < n; j++)
for (int i = 1; i < m; i++)
dp[i] += dp[i - 1];
return dp[m - 1];
}
这里的dp[i]+=dp[i-1];实际上就是dp[i]=dp[i]+dp[i-1],我们可以这样理解,上面的网格我们是一行一行计算的,**dp[i]**也就是上面深色的表示的是当前位置上面的值,dp[i-1]表示的是当前位置左边的值。
递归方式
这题除了动态规划以外,还可以把上面的动态规划改为递归的方式
public int uniquePaths(int m, int n) {
return uniquePathsHelper(1, 1, m, n);
}
//第i行第j列到第m行第n列共有多少种路径
public int uniquePathsHelper(int i, int j, int m, int n) {
//边界条件的判断
if (i > m || j > n)
return 0;
if ((i == m && j == n))
return 1;
//从右边走有多少条路径
int right = uniquePathsHelper(i + 1, j, m, n);
//从下边走有多少条路径
int down = uniquePathsHelper(i, j + 1, m, n);
//返回总的路径
return right + down;
}
代码中有注释,很容易理解,但其实这种效率很差,因为他包含了大量的重复计算,我们画个图来看一下。
我们看到上面图中红色,黑色,还有那种什么颜色的都表示重复的计算,所以有一种方式就是把计算过的值使用一个map存储起来,用的时候先查看是否计算过,如果计算过就直接拿来用,看下代码
public int uniquePaths(int m, int n) {
return uniquePathsHelper(1, 1, m, n, new HashMap<>());
}
public int uniquePathsHelper(int i, int j, int m, int n, Map<String, Integer> map) {
if (i > m || j > n)
return 0;
if ((i == m && j == n))
return 1;
String key = i + "*" + j;
if (map.containsKey(key))
return map.get(key);
int right = uniquePathsHelper(i + 1, j, m, n, map);
int down = uniquePathsHelper(i, j + 1, m, n, map);
int totla = right + down;
map.put(key, totla);
return totla;
}
使用公式计算
我们要想到达终点,需要往下走n-1步,往右走m-1步,总共需要走n+m-2步。他无论往右走还是往下走他的总的步数是不会变的。也就相当于总共要走n+m-2步,往右走m-1步总共有多少种走法,很明显这就是一个排列组合问题,公式如下
排列组合的计算公式如下
公式为(m+n-2)! / [(m-1)! * (n-1)!]
代码如下
public int uniquePaths(int m, int n) {
int N = n + m - 2;
double res = 1;
for (int i = 1; i < m; i++)
res = res * (N - (m - 1) + i) / i;
return (int) res;
}
总结
这题使用动态规划是最容易理解也是最容易解决的,当然后面的递归和公式计算也能解决。
上一篇: 动态规划:62. 不同路径