机器人从(0 , 0)
位置出发,到(m - 1, n - 1)终点。按照动规五部曲来分析:
1.确定dp数组(dp table)以及下标的含义
dp[i][j]
:表示从(0 ,0)
出发,到(i, j)
有dp[i][j]
条不同的路径。
2.确定递推公式
想要求dp[i][j]
,只能有两个方向来推导出来,即dp[i - 1][j]
和 dp[i][j - 1]
。
此时在回顾一下 dp[i - 1][j]
表示啥,是从(0, 0)
的位置到(i - 1, j)
有几条路径;dp[i][j - 1]
同理。
那么很自然,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
,因为dp[i][j]
只有这两个方向过来。
3.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数组
如图所示:
代码:
int uniquePaths(int m, int n)
{
// 1.构造dp数组
vector<vector<int>> dp(m, vector<int>(n, 0));
// 2.初始化dp数组
for(int i = 0; i < m; i++)
dp[i][0] = 1;
for(int j = 0; j < n; j++)
dp[0][j] = 1;
// 3.从上到下、从左到右遍历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]; // 递推公式
return dp[m-1][n-1]; // 返回最后结果
}
这道题相对于62.不同路径就是有了障碍。
第一次接触这种题目的同学可能会有点懵,这有障碍了,应该怎么算呢?
62.不同路径中我们已经详细分析了没有障碍的情况,有障碍的话,其实就是标记对应的dp table(dp数组)保持初始值(0)就可以了。
注意:障碍物的地方dp数组保持是0的话,对计算到达当前障碍物的位置有几条路径来说是没有意义的。而是对于计算下一个位置的时候,比如当前障碍物的下一行对应位置,那么肯定不能从当前障碍物位置到达了,但是代码中我们还是要相加障碍物这个位置的结果,所以让它保持是0就没问题了。
动规五部曲:
1.确定dp数组(dp table)以及下标的含义
dp[i][j]
:表示从(0 ,0)
出发,到(i, j)
有dp[i][j]
条不同的路径。
2.确定递推公式
递推公式和62.不同路径一样,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
。
但这里需要注意一点,因为有了障碍,(i, j)
如果就是障碍的话应该就保持初始状态(初始状态为0)。
所以代码为:
if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j]
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
3.dp数组如何初始化
在62.不同路径不同路径中我们给出如下的初始化:
vector<vector<int>> dp(m, vector<int>(n, 0)); // 初始值为0
for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;
因为从(0, 0)
的位置到(i, 0)
的路径只有一条,所以dp[i][0]
一定为1,dp[0][j]
也同理。
但如果(i, 0)
这条边有了障碍之后,障碍之后(包括障碍)都是走不到的位置了,所以障碍之后的dp[i][0]
应该还是初始值0。如下图所示:
下标(0, j)
的初始化情况同理。
所以本题初始化代码为:
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;
注意代码里for循环的终止条件,一旦遇到obstacleGrid[i][0] == 1
的情况就停止dp[i][0]
的赋值1的操作,dp[0][j]
同理。
4.确定遍历顺序
从递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
中可以看出,一定是从左到右一层一层遍历,这样保证推导dp[i][j]
的时候,dp[i - 1][j] 和 dp[i][j - 1]
一定是有数值。
代码如下:
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数组
这里就不举例了。
最后给出代码如下,非常简单:
int uniquePathsWithObstacles(vector<vector<int>> &obstacleGrid)
{
// 1.定义dp数组
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
// 2.初始化dp数组,注意一旦遇到障碍物,直接不满足for循环,for循环就退出了,然后dp就保持原来的数值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;
// 3.从上到下,从左到右遍历每一个位置
for(int i = 1; i < m; i++)
for(int j = 1; j < n; j++)
// 注意:当前位置是空地才计算路径条数,如果是障碍物,则直接保持原来的dp是0的数值
if(obstacleGrid[i][j] == 0)
dp[i][j] = dp[i-1][j] + dp[i][j-1];
return dp[m-1][n-1];
}