63. 不同路径 II

63. 不同路径 II

63. 不同路径 II

给定一个 m x n 的整数数组 grid。一个机器人初始位于 左上角(即 grid[0][0])。机器人尝试移动到 右下角(即 grid[m - 1][n - 1])。机器人每次只能向下或者向右移动一步。

网格中的障碍物和空位置分别用 10 来表示。机器人的移动路径中不能包含 任何 有障碍物的方格。

返回机器人能够到达右下角的不同路径数量。

测试用例保证答案小于等于 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]一定为1dp[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的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值