文章目录
63. 不同路径 II
给定一个 m x n
的整数数组 grid
。一个机器人初始位于 左上角(即 grid[0][0]
)。机器人尝试移动到 右下角(即 grid[m - 1][n - 1]
)。机器人每次只能向下或者向右移动一步。
网格中的障碍物和空位置分别用 1
和 0
来表示。机器人的移动路径中不能包含 任何 有障碍物的方格。
返回机器人能够到达右下角的不同路径数量。
测试用例保证答案小于等于 2 * 10^9
。
示例 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.不同路径
就是有了障碍物。
第一次接触这种题目的同学可能会有点懵,这有障碍了,应该怎么算呢?
62.不同路径
中我们已经详细分析了没有障碍的情况,有障碍的话,其实就是标记对应的dp数组
保持初始值(0)
就可以了。
动规五部曲
1.确定dp数组以及下标的含义
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] == 1{
dp[i][j] = 0 // 障碍物处不可到达
} else {
dp[i][j] = dp[i-1][j] + dp[i][j-1]
}
3.dp数组如何初始化
在62.不同路径
不同路径中我们给出如下的初始化:
for j:= 0;j < n;j++{
dp[0][j] = 1 // 第一行
}
for i:= 0;i < m;i++{
dp[i][0] = 1 // 第一列
}
因为从(0, 0)
的位置到(i, 0)
的路径只有一条,所以dp[i][0]
一定为1
,dp[0][j]
也同理。
但如果(i, 0)
这条边有了障碍之后,障碍之后(包括障碍)都是走不到的位置了,所以障碍之后的dp[i][0]
应该还是初始值0。
如图:
下标(0, j)
的初始化情况同理。
所以本题初始化代码为:
// 初始化dp数组
// 注意当遇到障碍物后,在障碍物之后的格子,都是不可到达的
// 所以遇到障碍物可以立即退出for,让后续位置是默认值0
for j := 0;j < col && obstacleGrid[0][j] != 1;j++ {
dp[0][j] = 1
}
for i := 0;i < row && obstacleGrid[i][0] != 1;i++ {
dp[i][0] = 1
}
注意代码里for
循环的终止条件,一旦遇到obstacleGrid[i][0] == 1
的情况就终止for
循环,停止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 i := 1;i < row;i++ {
for j := 1;j < col;j++ {
if obstacleGrid[i][j] == 1{
dp[i][j] = 0 // 障碍物处不可到达
} else {
dp[i][j] = dp[i-1][j] + dp[i][j-1]
}
}
}
return dp[row-1][col-1]
5.举例推导dp数组
拿示例1来举例如题:
对应的dp table 如图:
如果这个图看不懂,建议再理解一下递归公式,然后照着文章中说的遍历顺序,自己推导一下!
动规五部分分析完毕,对应Go
代码如下:
func uniquePathsWithObstacles(obstacleGrid [][]int) int {
if len(obstacleGrid) == 0 || len(obstacleGrid[0]) == 0 {
return 0
}
// 初始化二维切片
row,col := len(obstacleGrid),len(obstacleGrid[0])
dp := make([][]int,row)
for i:=0;i < row;i++{
dp[i] = make([]int,col)
}
// 初始化dp数组
// 注意当遇到障碍物后,在障碍物之后的格子,都是不可到达的
// 所以遇到障碍物可以立即退出for,让后续位置是默认值0
for j := 0;j < col && obstacleGrid[0][j] != 1;j++ {
dp[0][j] = 1
}
for i := 0;i < row && obstacleGrid[i][0] != 1;i++ {
dp[i][0] = 1
}
for i := 1;i < row;i++ {
for j := 1;j < col;j++ {
if obstacleGrid[i][j] == 1{
dp[i][j] = 0 // 障碍物处不可到达
} else {
dp[i][j] = dp[i-1][j] + dp[i][j-1]
}
}
}
return dp[row-1][col-1]
}
时间复杂度:
O
(
n
×
m
)
O(n × m)
O(n×m),
n
n
n、
m
m
m 分别为
o
b
s
t
a
c
l
e
G
r
i
d
obstacleGrid
obstacleGrid 长度和宽度
空间复杂度:
O
(
n
×
m
)
O(n × m)
O(n×m)
总结
本题是62.不同路径
的障碍版,整体思路大体一致。
但就算是做过62.不同路径
,在做本题也会有感觉遇到障碍无从下手。
其实只要考虑到,遇到障碍dp[i][j]保持0就可以了。
也有一些小细节,例如:初始化的部分,很容易忽略了障碍之后应该都是0的情况。