来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/unique-paths
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
- 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右
- 向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3
输出:28
示例 4:
输入:m = 3, n = 3
输出:6
做动态规划的题时,如果无法一眼看出状态转移方程,就先写出暴力递归的尝试,然后改写暴力递归,把暴力递归改写成动态规划的难度,会比直接写动态规划的难度等级降一级.
这题的暴力递归还是比较容易写的,只有向下和向右两个方向做出选择,两个方向不同的情况相加就是总的情况.在写递归时,注意越界情况的处理.
还有一个注意事项,左上角是从(1,1)开始到(m,n)的.
/**
* 主方法调用
*/
public int uniquePaths(int m, int n) {
//左上角位置是1,1
return process(m,n,1,1);
}
/**
* 暴力递归
* i 和 j 来标记来到的位置
*/
public int process (int m,int n,int i ,int j){
//来到最后一个位置,返回1,前面走的路径有效
if(i == m && j == n ){
return 1;
}
//越界时,路径选择无效返回0
if(i > m || j > n){
return 0;
}
//向下走
int down = process(m,n,i + 1,j);
//向右走
int right = process(m,n,i,j + 1);
//返回两种情况之和
return down + right;
}
j解决递归中的重叠子问题,减少计算,一般情况下,递归加缓存的效率和动态规划的效率是一样的,能写到递归加缓存,就可以了.
/**
* 主方法调用
*/
public int uniquePaths1(int m, int n) {
//缓存表
int[][]dp = new int[m+1][n+1];
return process2(m,n,1,1,dp);
}
/**
* 加缓存 去除重复计算
* dp 缓存表
*/
public int process2 (int m,int n,int i ,int j,int[][]dp){
if(i == m && j == n ){
return 1;
}
if(i > m || j > n){
return 0;
}
//缓存有值直接从缓存中拿
if(dp[i][j] != 0){
return dp[i][j];
}
int down = process2(m,n,i + 1,j,dp);
int right = process2(m,n,i,j + 1,dp);
//结果存在缓存中
dp[i][j] = down + right;
return down + right;
}
这题可以从底向上去推.
从递归中的base case 中可以知道.最右下角是1.我们还可以知道最底下一行,无法向下走,只能向右,所以都是1.最右边一列,无法向右走,只能向下走,所以方法数也是1.
如图演示:
最后一行和最后一列都是1,再看x 位置如何得出:
根据暴力递归中,.
int down = process2(m,n,i + 1,j,dp);
int right = process2(m,n,i,j + 1,dp);
//结果存在缓存中
dp[i][j] = down + right;
可以看出x 位置依赖两个画三角符合的位置,因此可以得出状态转移方程是:
dp[i][j] = dp[i + 1][j] + dp[i][j + 1];
下面就可以写代码了:
/**
* 动态规划
*/
public int uniquePaths(int m, int n) {
int[][]dp = new int[m+1][n+1];
//初始化最后一行
for (int i = 1; i <= n;i++){
dp[m][i] = 1;
}
//初始化最后一列
for (int i = 1; i <= m;i++){
dp[i][n] = 1;
}
for (int i = m - 1;i >= 0;i--){
for (int j = n - 1; j >= 0;j-- ){
//状态转移方程
dp[i][j] = dp[i + 1][j] + dp[i][j + 1];
}
}
return dp[1][1];
}
leetcode877. 石子游戏
leetcode64. 最小路径和
leetcode416. 分割等和子集
leetcode354. 俄罗斯套娃信封问题
leetcode300. 最长递增子序列
leetcode337. 打家劫舍 III