前言
动态规划第6篇。记录 七十七【63. 不同路径 II】
一、题目阅读
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。
示例 1:
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
示例 2:
输入:obstacleGrid = [[0,1],[0,0]]
输出:1
提示:
m == obstacleGrid.length
n == obstacleGrid[i].length
1 <= m, n <= 100
obstacleGrid[i][j] 为 0 或 1
二、尝试实现
分析题目,确定方法
- 记录 七十四【62.不同路径】与这道题类似,只不过本题的网格中多了障碍物,无法通过。这属于“跳房子”、“爬楼梯”类型的题目,所以是状态转移类型,有状态转移公式,应该用动态规划完成。
动态规划实现
- 第一步:确定dp数组含义和下标含义。定义一个二维数组dp[i][j],对应网络二维图形。数组含义:到第i+1行第j+1列有dp[i][j]条路径。
- 第二步:确定状态转移公式。dp[i][j] = dp[i-1][j] + dp[i][j-1];第一个加数代表向下走一步,第二个加数代表向右走一步。但是障碍物的地方怎么处理呢?
- 如下图,蓝色方格和黄色方格是紧挨着障碍物的网格。其余位置的上方和左方没有障碍物,所以递推公式同样适用。
- 蓝色方格只能从上放向下一步,左侧无法通过;黄色方格只能从左向右一步,上方无法通过。
- 如此dp[1][1]障碍物的位置dp值为0,作为加数并不影响状态方程。
- 所以:对于有障碍物的网格,dp = 0.
- 第三步:确定初始化。没有障碍物时,初始化第一行和第一列,dp =1;有障碍物时:
- 如果是空位置,dp可以和前一个相等。
- 如果遇到障碍物,dp =0;
- 所以逻辑如下:第一行和第一列同一个道理。
- (可能初次做题会按照没有障碍物的情况初始化,但是提交用例不通过,就需要重新改正)
for(int i = 0;i < m;i++){//初始化第一列
if(i > 0 && obstacleGrid[i][0] == 0){//没有障碍物的地方
dp[i][0] = dp[i-1][0];
}else if(obstacleGrid[i][0] == 1){
dp[i][0] = 0;
}else{
dp[i][0] = 1;
}
}
4. 遍历顺序:从递推公式知道,从前往后,从上到下。
代码实现
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int result;//返回值
int m = obstacleGrid.size();//有m行
int n = obstacleGrid[0].size();//有n列
//定义dp[i][j]数组。含义是到第i+1行第j+1列有有dp[i][j]条路径。遇到障碍物的地方dp[i][j]=0.代表该位置无妨通过。
vector<vector<int>> dp(m,vector<int> (n,0));//建立数组是初始化为0;
//dp初始化.
for(int i = 0;i < m;i++){//初始化第一列
if(i > 0 && obstacleGrid[i][0] == 0){//没有障碍物的地方
dp[i][0] = dp[i-1][0];
}else if(obstacleGrid[i][0] == 1){
dp[i][0] = 0;
}else{
dp[i][0] = 1;
}
}
for(int i = 0;i < n;i++){//初始化第一行
if(i >0 && obstacleGrid[0][i] == 0){//没有障碍物的地方
dp[0][i] = dp[0][i-1];
}else if(obstacleGrid[0][i] == 1){
dp[0][i] = 0;
}else{
dp[0][i] = 1;
}
}
//遍历顺序
for(int i = 1;i < m;i++){//外层是行
for(int j = 1;j < n;j++){//内层是列
if(obstacleGrid[i][j] == 0){
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}else{
dp[i][j] = 0;
}
}
}
return dp[m-1][n-1];
}
};
改进dp数组
- 记录 七十四【62.不同路径】改进dp数组变成一维。那么这里也尝试一下。
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int result;//返回值
int m = obstacleGrid.size();//有m行
int n = obstacleGrid[0].size();//有n列
//定义dp[i]数组。滚动数组,每一行都更新。含义是到某一行第i+1列有dp[i][j]条路径。
vector<int> dp(n,0);//建立数组是初始化为0;
//dp初始化.
for(int i = 0;i < n;i++){//初始化第一行
if(i >0 && obstacleGrid[0][i] == 0){//没有障碍物的地方
dp[i] = dp[i-1];
}else if(obstacleGrid[0][i] == 1){
dp[i] = 0;
}else{
dp[i] = 1;
}
}
//遍历顺序
for(int i = 1;i < m;i++){//外层是行
for(int j = 0;j < n;j++){//内层是列
if(j > 0 && obstacleGrid[i][j] == 0){
dp[j] += dp[j-1];
}else if(obstacleGrid[i][j] == 1){
dp[j]= 0;
}
}
}
return dp[n-1];
}
};
三、参考学习
学习内容
- 思路和操作都是一致的,但是代码处理上可以改进。
- 改进初始化:因为在vector<vector> dp(m,vector (n,0));//方格初始化是0。所以一些赋值0的操作可以跳过,维持原状。
for(int i = 0;i < m && obstacleGrid[i][0] == 0;i++){//初始化第一列
dp[i][0] = 1;
}
for(int i = 0;i < n && obstacleGrid[0][i] == 0;i++){//初始化第一行
dp[0][i] = 1;
}
- 递推公式:可以把else不要。
for(int i = 1;i < m;i++){//外层是行
for(int j = 1;j < n;j++){//内层是列
if(obstacleGrid[i][j] == 0){
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}else{
dp[i][j] = 0;
}
}
}
改成
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];
}
}
- 同理,把二、改进dp数组代码也重新改下初始化。
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int result;//返回值
int m = obstacleGrid.size();//有m行
int n = obstacleGrid[0].size();//有n列
//定义dp[i]数组。滚动数组,每一行都更新。含义是到某一行第i+1列有dp[i][j]条路径。
vector<int> dp(n,0);//建立数组是初始化为0;
//dp初始化.
for(int i = 0;i < n && obstacleGrid[0][i] == 0;i++){//初始化第一行
dp[i] = 1;
}
//遍历顺序
for(int i = 1;i < m;i++){//外层是行
for(int j = 0;j < n;j++){//内层是列
if(j > 0 && obstacleGrid[i][j] == 0){
dp[j] += dp[j-1];
}else if(obstacleGrid[i][j] == 1){
dp[j]= 0;
}
}
}
return dp[n-1];
}
};
总结
对比 记录 七十四【62.不同路径】。
(欢迎指正,转载标明出处)