LeetCode 第63题:不同路径 II
题目描述
一个机器人位于一个 m x n
网格的左上角(起始点在下图中标记为 "Start" )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 "Finish")。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1
和 0
来表示。
难度
中等
题目链接
示例
示例 1:
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]] 输出:2 解释:3x3 网格的正中间有一个障碍物。 从左上角到右下角一共有 2 条不同的路径:
- 向右 -> 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右 -> 向右
示例 2:
输入:obstacleGrid = [[0,1],[0,0]] 输出:1
提示
m == obstacleGrid.length
n == obstacleGrid[i].length
1 <= m, n <= 100
obstacleGrid[i][j]
为0
或1
解题思路
动态规划
这是"不同路径"的变体,需要考虑障碍物的影响。我们仍然可以使用动态规划,但需要特别处理障碍物的情况。
关键点:
- 如果起点或终点有障碍物,直接返回0
- 遇到障碍物时,该位置的路径数为0
- 第一行和第一列的处理需要特别注意
- 可以复用输入数组来节省空间
具体步骤:
- 检查起点是否有障碍物
- 初始化第一行和第一列
- 动态规划计算每个位置的路径数
- 注意处理障碍物的特殊情况
图解思路
算法步骤分析表
步骤 | 操作 | 状态 | 说明 |
---|---|---|---|
初始 | 检查 | [[0,0,0],[0,1,0],[0,0,0]] | 原始网格 |
第1步 | 初始化 | [[1,1,1],[0,0,0],[0,0,0]] | 处理第一行 |
第2步 | 计算 | [[1,1,1],[1,0,1],[0,0,0]] | 处理第二行 |
最终 | 完成 | [[1,1,1],[1,0,1],[1,1,2]] | 得到结果 |
状态/情况分析表
情况 | 输入 | 输出 | 说明 |
---|---|---|---|
起点障碍 | [[1,0]] | 0 | 无法开始 |
终点障碍 | [[0,1]] | 0 | 无法到达 |
中间障碍 | [[0,0],[0,1]] | 0 | 被阻断 |
代码实现
C# 实现
csharp
public class Solution {
public int UniquePathsWithObstacles(int[][] obstacleGrid) {
if (obstacleGrid == null || obstacleGrid.Length == 0) return 0;
int m = obstacleGrid.Length;
int n = obstacleGrid[0].Length;
// 如果起点或终点有障碍,直接返回0
if (obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1) return 0;
// 使用long避免整数溢出
long[] dp = new long[n];
// 初始化第一个位置
dp[0] = 1;
// 处理每一行
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
dp[j] = 0;
} else if (j > 0) {
dp[j] += dp[j-1];
}
}
}
return (int)dp[n-1];
}
}
Python 实现
python
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
if not obstacleGrid or not obstacleGrid[0]:
return 0
m, n = len(obstacleGrid), len(obstacleGrid[0])
# 如果起点或终点有障碍,直接返回0
if obstacleGrid[0][0] == 1 or obstacleGrid[m-1][n-1] == 1:
return 0
# 使用一维数组优化空间复杂度
dp = [0] * n
dp[0] = 1
# 处理每一行
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 1:
dp[j] = 0
elif j > 0:
dp[j] += dp[j-1]
return dp[n-1]
C++ 实现
cpp
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
if (obstacleGrid.empty() || obstacleGrid[0].empty()) return 0;
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
// 如果起点或终点有障碍,直接返回0
if (obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1) return 0;
// 使用long避免整数溢出
vector<long> dp(n, 0);
dp[0] = 1;
// 处理每一行
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
dp[j] = 0;
} else if (j > 0) {
dp[j] += dp[j-1];
}
}
}
return dp[n-1];
}
};
执行结果
- 执行用时:80 ms
- 内存消耗:37.5 MB
代码亮点
- 🎯 使用一维数组优化空间复杂度
- 💡 使用long类型避免溢出
- 🔍 巧妙处理障碍物情况
- 🎨 代码结构清晰简洁
常见错误分析
- 🚫 没有检查起点和终点的障碍物
- 🚫 整数溢出问题
- 🚫 边界条件处理不当
- 🚫 障碍物处理逻辑错误
解法对比
解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|
DFS递归 | O(2^(m+n)) | O(m+n) | 直观易懂 | 超时 |
二维DP | O(mn) | O(mn) | 容易理解 | 空间消耗大 |
一维DP | O(mn) | O(n) | 最优解法 | 不够直观 |
原地修改 | O(mn) | O(1) | 空间最优 | 修改输入 |