动态规划—63. 不同路径 II
- 前言
- 题目描述
- 基本思路
- 1. 问题定义:
- 2. 理解问题和递推关系:
- 3. 解决方法:
- 3.1 动态规划方法
- 3.2 空间优化的动态规划
- 4. 进一步优化:
- 5. 小总结:
- 代码实现
- Python3代码实现
- Python 代码解释
- C++代码实现
- C++ 代码解释
- 总结:
前言
本文将探讨“不同路径 II”这一问题,定义了在一个有障碍物的网格中,从起点到终点的路径数计算方法。我们将从基本概念入手,逐步深入理解问题的递推关系、动态规划的解决方案及其优化方法。通过详细的解释和代码示例,我们希望能帮助读者更好地掌握动态规划的思想与应用,提升解决复杂问题的能力。
题目描述
基本思路
1. 问题定义:
给定一个 m ∗ n m * n m∗n 的网格,其中有一些障碍物。机器人从左上角(起始点)出发,只能向下或向右移动,目标是到达右下角(终点)。问总共有多少条不同的路径可以到达终点,路径上不得经过障碍物。
2. 理解问题和递推关系:
- 问题的本质是计算从起点到终点的所有可能路径数,但需要考虑障碍物的影响。
- 关键观察:到达某个点的路径数等于到达其上方点的路径数加上到达其左方点的路径数,但若某个点是障碍物,则路径数为0。
- 递推关系:设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示到达位置
(
i
,
j
)
(i, j)
(i,j) 的不同路径数,则:
- 如果该位置是障碍物, d p [ i ] [ j ] = 0 dp[i][j] = 0 dp[i][j]=0;
- 否则, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j] = dp[i-1][j] + dp[i][j-1] dp[i][j]=dp[i−1][j]+dp[i][j−1]。
- 边界条件:第一行和第一列的路径数初始化为 1 1 1,若位置是障碍物则设为 0 0 0。
3. 解决方法:
3.1 动态规划方法
- 创建一个 m ∗ n m * n m∗n 的二维数组 dp。
- 初始化第一行和第一列的值为 1 1 1(遇到障碍物时设为 0 0 0)。
- 从左上到右下填充 dp 数组,使用递推公式: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j] = dp[i-1][j] + dp[i][j-1] dp[i][j]=dp[i−1][j]+dp[i][j−1],考虑障碍物的情况。
- 最终结果为 d p [ m − 1 ] [ n − 1 ] dp[m-1][n-1] dp[m−1][n−1]。
3.2 空间优化的动态规划
- 使用一维数组 dp 代替二维数组。
- 初始化 dp 数组的所有元素为0,第一列和第一行根据障碍物初始化。
- 逐行更新 dp 数组,每次更新时,当前值等于其自身(上一行的值)加上前一个元素(左边的值)。
- 最终结果为 dp[n-1]。
4. 进一步优化:
- 尽管空间优化已减少了复杂度,仍然可以考虑:
- 如果输入很大,可以采用更高效的数学模型或其他算法,但在此问题中,动态规划已较为高效。
5. 小总结:
- 动态规划方法提供了直观且易于理解的解法,适用于理解路径问题的本质。
- 空间优化的方法显著降低了空间复杂度,适用于处理大规模输入。
- 这个问题是动态规划的经典案例,掌握它对于解决类似问题非常有帮助。理解障碍物的处理逻辑是动态规划中的重要技巧。
以上就是不同路径II 问题的基本思路。
代码实现
Python3代码实现
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: list[list[int]]) -> int:
if not obstacleGrid or not obstacleGrid[0]:
return 0
rows, cols = len(obstacleGrid), len(obstacleGrid[0])
# 创建 dp 数组
dp = [[0] * cols for _ in range(rows)]
# 初始化第一行
for j in range(cols):
if obstacleGrid[0][j] == 1:
break # 遇到障碍物,后续路径数为0
dp[0][j] = 1
# 初始化第一列
for i in range(rows):
if obstacleGrid[i][0] == 1:
break # 遇到障碍物,后续路径数为0
dp[i][0] = 1
# 填充 dp 数组
for i in range(1, rows):
for j in range(1, cols):
if obstacleGrid[i][j] == 1:
dp[i][j] = 0 # 障碍物
else:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[rows-1][cols-1]
Python 代码解释
在 Python 实现中,我们创建了一个二维数组 dp 用于存储到达每个点的路径数。首先初始化第一行和第一列,如果遇到障碍物,则设置路径数为0。接着,使用双重循环填充 dp 数组,根据之前讨论的递推关系进行计算。最终,返回右下角的路径数,即 dp[rows-1][cols-1]。
C++代码实现
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
if (obstacleGrid.empty() || obstacleGrid[0].empty()) return 0;
int rows = obstacleGrid.size();
int cols = obstacleGrid[0].size();
// 创建 dp 数组
vector<vector<int>> dp(rows, vector<int>(cols, 0));
// 初始化第一行
for (int j = 0; j < cols; ++j) {
if (obstacleGrid[0][j] == 1) break; // 遇到障碍物
dp[0][j] = 1;
}
// 初始化第一列
for (int i = 0; i < rows; ++i) {
if (obstacleGrid[i][0] == 1) break; // 遇到障碍物
dp[i][0] = 1;
}
// 填充 dp 数组
for (int i = 1; i < rows; ++i) {
for (int j = 1; j < cols; ++j) {
if (obstacleGrid[i][j] == 1) {
dp[i][j] = 0; // 障碍物
} else {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[rows-1][cols-1];
}
};
C++ 代码解释
C++ 实现采用了 STL 的 vector 来动态创建和存储二维数组。与 Python 类似,我们首先初始化第一行和第一列的路径数,然后在嵌套循环中使用递推关系填充 dp 数组。C++ 的语法相对严格,但通过合理的注释,读者可以清楚地理解每一步的逻辑。
总结:
动态规划方法为解决带障碍物的路径问题提供了清晰且高效的解决方案。通过对 Python 和 C++ 代码的实现,我们展示了如何利用动态规划思想来应对复杂问题。掌握这些技巧不仅能帮助我们解决类似的路径问题,也为理解更复杂的动态规划问题打下了坚实的基础。