附一个动态规划五部曲在这里
- 确定dp数组下标及值的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序(遍历循环外层是啥内层是啥,遍历从左向右还是从右向左)
- 打印dp数组,用于 debug
62.不同路径
本题要用二维的 dp 数组
1、确定 dp 数组下标及值的含义
dp[i][j]:下标 i, j 表示位于行索引为 i,列索引为 j 的位置,dp[i][j] 的值表示到达该位置的不同路径条数
2、确定递推公式
想求 dp[i][j],这个 i, j 表示机器人所处的位置,根据题目描述,可以从位置 i - 1, j 向下走到位置 i, j;也可以从位置 i, j - 1 向右走到位置 i, j
故到达位置 i, j 的不同路径条数 dp[i][j] 为到达位置 i - 1, j 的不同路径条数 dp[i - 1][j] 加上到达位置 i, j - 1 的不同路径条数 dp[i][j - 1],即 dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
在推导 dp[i][j] 的时候,一定要时刻想着 dp 数组下标及值的含义,否则容易跑偏
3、dp 数组初始化
本题遍历更新 dp 数组需要用到左边的值和上方的值,因此需要初始化 dp 数组最左边一列和最上方一行的值
如何初始化呢,根据 dp 数组下标及值的含义,首先 dp[i][0] 一定都是1,因为从 0, 0 的位置到 i, 0 的路径只有一条,那么 dp[0][j] 也同理
for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;
4、确定遍历顺序
这里要看一下递推公式 dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j] 都是从其上方和左方推导而来,那么我们从左到右从上层到下层遍历就可以了
这样就可以保证推导 dp[i][j] 的时候,dp[i - 1][j] 和 dp[i][j - 1] 一定是已经被更新后的正确数值
5、打印 dp 数组验证
代码如下
class Solution {
public:
int uniquePaths(int m, int n) {
// 1、定义dp数组下标及值含义:下标表示位置,值表示到该位置的不同路径
vector<vector<int> > dp(m, vector<int>(n));
// 2、递推公式 dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
// 3、初始化第一列和第一行为1
for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;
// 4、遍历更新dp数组
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];
// 5、打印验证dp数组,略
return dp[m - 1][n - 1]; // 根据题意,答案就是dp[m - 1][n - 1]
}
};
63.不同路径 II
本题和62.不同路径类似思路大体上是相同的,还是按照动态规划五部曲分析
1、确定 dp 数组下标及值的含义
dp[i][j]:下标 i, j 表示位于行索引为 i,列索引为 j 的位置,dp[i][j] 的值表示到达该位置的不同路径条数
2、确定递推公式
想求 dp[i][j],这个 i, j 表示机器人所处的位置,根据题目描述,可以从位置 i - 1, j 向下走到位置 i, j;也可以从位置 i, j - 1 向右走到位置 i, j
但这里需要注意一点,因为有了障碍,位置 i, j 如果有障碍的话,到达这个位置的不同路径条数一定为 0,因此,某个位置有障碍的话,dp 数组应该保持初始状态(初始状态为0),没有障碍的时候才利用递推公式更新 dp 数组
if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j]
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
3、dp 数组初始化
和上一道题同理,本题遍历更新 dp 数组需要用到左边的值和上方的值,因此需要初始化 dp 数组最左边一列和最上方一行的值
和上一道题不同的是,因为机器人只能向右或者向下,如果某条边(指第一行或第一列)有障碍物,障碍之后(包括障碍)都是走不到的位置了,所以障碍之后(指第一行的右边或第一列的下边)的 dp 数组应该就是初始值 0
以初始化第一行的情况为例
本题的初始化代码为
vector<vector<int>> dp(m, vector<int>(n, 0));
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;
4、确定遍历顺序
从左到右从上层到下层遍历就可以了,这样就可以保证推导 dp[i][j] 的时候,dp[i - 1][j] 和 dp[i][j - 1] 一定是已经被更新后的正确数值
5、打印 dp 数组验证
代码如下
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size();//行
int n = obstacleGrid[0].size();//列
if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1) //如果在起点或终点出现了障碍,直接返回0
return 0;
// 1、定义dp数组下标及值的含义
vector<vector<int>> dp(m, vector<int>(n, 0));
// 2、递推公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
// 3、初始化,注意有障碍
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;
// 4、遍历更新dp数组,注意有障碍
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (obstacleGrid[i][j] == 1) continue;
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
// 5、打印dp数组略
return dp[m - 1][n - 1];
}
};
回顾总结
今天用的二维 dp 数组,两个下标的含义:用来表示所处的位置状态