# 「力扣」第 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
# 方法一:动态规划
参考代码:
from typing import List
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m = len(obstacleGrid)
if m == 0:
return 0
n = len(obstacleGrid[0])
dp = [[0 for _ in range(n)] for _ in range(m)]
if obstacleGrid[0][0] == 1:
return 0
else:
dp[0][0] = 1
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 1:
dp[i][j] = 0
continue
if i == 0:
if j == 0:
continue
dp[0][j] = dp[0][j - 1]
elif j == 0:
dp[i][0] = dp[i - 1][0]
else:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[-1][-1]
if __name__ == '__main__':
obstacleGrid = [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
s = Solution()
res = s.uniquePathsWithObstacles(obstacleGrid)
print(res)
总结:
- 表格复用技巧:使用一维数组记录状态值;
- 一维数组 + 哨兵。
参考代码:
Java 代码:
public class Solution {
// 空间复杂度:O(N),N 是矩阵的列数
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
if (m == 0) {
return 0;
}
int n = obstacleGrid[0].length;
int[] dp = new int[n + 1];
// 技巧:回避了对边界条件的判断
dp[1] = 1;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
dp[j + 1] = 0;
} else {
dp[j + 1] += dp[j];
}
}
}
return dp[n];
}
}
Python 代码:
from typing import List
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m = len(obstacleGrid)
if m == 0:
return 0
n = len(obstacleGrid[0])
if obstacleGrid[0][0] == 1:
return 0
dp = [0] * n
# 这一步不要忘记了
dp[0] = 1
# 再写后面几行
for row in range(m):
for col in range(n):
# 【就分下面这两种情况就可以了】
if obstacleGrid[row][col] == 1:
dp[col] = 0
elif col > 0:
dp[col] += dp[col - 1]
else:
# 第 0 列不是 0 就是 1
# 0 的情况首先判断了
# 什么都不做
pass
return dp[-1]
# 方法二:记忆化递归
参考代码:
class Solution:
def __init__(self):
self.cached = []
self.obstacleGrid = None
def _path_ways(self, i, j):
if self.cached[i][j] != 0:
return self.cached[i][j]
if self.obstacleGrid[i][j] == 1:
return 0
if i == 0 and j == 0:
return 1
if i == 0:
ways = self._path_ways(0, j - 1)
elif j == 0:
ways = self._path_ways(i - 1, 0)
else:
ways = self._path_ways(i, j - 1) + self._path_ways(i - 1, j)
self.cached[i][j] = ways
return ways
def uniquePathsWithObstacles(self, obstacleGrid):
"""
:type obstacleGrid: List[List[int]]
:rtype: int
"""
m = len(obstacleGrid)
if m == 0:
return 0
n = len(obstacleGrid[0])
self.cached = [[0 for _ in range(n)] for _ in range(m)]
self.obstacleGrid = obstacleGrid
return self._path_ways(m - 1, n - 1)
作者:liweiwei1419 链接:https://suanfa8.com/dynamic-programming/solutions/0063-unique-paths-ii 来源:算法吧 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。