《代码随想录》Ⅸ 动态规划 63. 不同路径 II
努力学习!
题目:力扣链接
-
给定一个
m x n
的整数数组grid
。一个机器人初始位于 左上角(即grid[0][0]
)。机器人尝试移动到 右下角(即grid[m - 1][n - 1]
)。机器人每次只能向下或者向右移动一步。网格中的障碍物和空位置分别用
1
和0
来表示。机器人的移动路径中不能包含 任何 有障碍物的方格。返回机器人能够到达右下角的不同路径数量。
测试用例保证答案小于等于
2 * 10e9
。
一、思想
这道题的核心思想是使用动态规划来解决带障碍物的网格路径计数问题。与基础版不同之处在于需要考虑障碍物的存在,当遇到障碍物时路径数变为0。通过分析机器人只能向右或向下移动的特性,可以发现到达每个无障碍物格子的路径数等于其上方格子和左方格子路径数之和。
二、代码
class Solution
{
public:
int uniquePathsWithObstacles(vector<vector<int>> &obstacleGrid)
{
// 初始化dp数组,dp[i][j]表示到达(i,j)格子的不同路径数
// 初始全部设为0,因为可能有障碍物阻挡
vector<vector<int>> dp(obstacleGrid.size(), vector<int>(obstacleGrid[0].size(), 0));
int m = obstacleGrid.size(); // 网格行数
int n = obstacleGrid[0].size(); // 网格列数
// 初始化第一列:如果没有障碍物则路径数为1,遇到障碍物则后面都为0
for (int i = 0; i < m; i++)
{
if (obstacleGrid[i][0] == 1) // 遇到障碍物
break;
dp[i][0] = 1; // 无障碍物则路径数为1
}
// 初始化第一行:如果没有障碍物则路径数为1,遇到障碍物则后面都为0
for (int j = 0; j < n; j++)
{
if (obstacleGrid[0][j] == 1) // 遇到障碍物
break;
dp[0][j] = 1; // 无障碍物则路径数为1
}
// 动态规划填充其他格子
for (int i = 1; i < m; i++)
{
for (int j = 1; j < n; j++)
{
if (obstacleGrid[i][j] == 0) // 当前格子无障碍物
{
// 路径数等于上方格子路径数加左方格子路径数
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
else // 当前格子有障碍物
{
dp[i][j] = 0; // 路径数为0
}
}
}
// 返回到达右下角格子的路径数
return dp[m - 1][n - 1];
}
};
三、代码解析
1. 算法工作原理分解
1.1 基本情况处理
- 初始化第一列:从起点向下,遇到障碍物前路径数为1,遇到后路径数为0且后续格子不可达
- 初始化第一行:从起点向右,遇到障碍物前路径数为1,遇到后路径数为0且后续格子不可达
- 特别注意起点或终点本身就是障碍物的情况(代码中已隐含处理)
1.2 动态规划数组初始化
- 创建m×n的二维dp数组,初始值全为0
- 数组大小与障碍物网格大小一致
1.3 递推计算
-
双重循环遍历网格内部(i从1到m-1,j从1到n-1):
- 如果当前格子无障碍物:dp[i][j] = dp[i-1][j] + dp[i][j-1]
- 如果当前格子有障碍物:dp[i][j] = 0
2. 关键点说明
-
障碍物处理:遇到障碍物时路径数直接置0,且会阻断后续路径
-
边界条件:第一行和第一列需要单独初始化,遇到障碍物后后续格子不可达
-
空间优化:可以使用一维滚动数组将空间复杂度优化到O(n)
-
特殊情况:
- 起点(0,0)有障碍物:直接返回0
- 终点(m-1,n-1)有障碍物:最终返回0
-
初始化技巧:dp数组初始全0,简化了障碍物的处理逻辑
四、复杂度分析
-
时间复杂度:O(mn)
- 需要双重循环遍历整个网格
- 每个格子处理时间为常数时间
-
空间复杂度:O(mn)
- 使用二维dp数组存储中间结果
- 可优化为O(n)空间:只需维护前一行的状态
白展堂:人生就是这样,苦和累你总得选一样吧?哪有什么好事都让你一个人占了呢。 ——《武林外传》