leetcode 62不同路径
一、二维 DP(基础版本)
1. 完整代码(精简注释)
class Solution {
public:
int uniquePaths(int m, int n) {
// dp[i][j]:到达位置 (i,j) 的路径数
vector<vector<int>> dp(m, vector<int>(n, 1));
// 从第二行、第二列开始遍历
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
// 当前路径数 = 上方路径数 + 左方路径数
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
// 返回右下角的路径数
return dp[m-1][n-1];
}
};
2. 动态规划五步总结
(1)确定 DP 数组及其含义
dp[i][j] 表示从左上角 (0,0) 出发,到达位置 (i,j) 的不同路径数量。
(2)确定递推公式
由于机器人只能向右或者向下移动,因此到达当前位置只有两种方式:
-
从上方
(i-1,j)向下移动。 -
从左方
(i,j-1)向右移动。
因此:
dp[i][j] = dp[i-1][j] + dp[i][j-1];
即:当前位置的路径数 = 上方路径数 + 左方路径数。
(3)DP 数组初始化
vector<vector<int>> dp(m, vector<int>(n, 1));
将所有元素初始化为 1。
原因:
-
第一行只能一直向右走,因此路径数为 1。
-
第一列只能一直向下走,因此路径数为 1。
-
其余位置会通过递推公式重新计算。
(4)确定遍历顺序
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
从上到下、从左到右遍历。
因为计算当前位置时,需要使用上方和左方已经计算好的结果。
(5)举例推导 DP 数组
假设:
m = 3;
n = 4;
最终 DP 数组:
1 1 1 1
1 2 3 4
1 3 6 10
例如:
dp[1][1] = dp[0][1] + dp[1][0] = 2;
dp[1][2] = dp[0][2] + dp[1][1] = 3;
dp[2][2] = dp[1][2] + dp[2][1] = 6;
dp[2][3] = dp[1][3] + dp[2][2] = 10;
最终返回 dp[2][3] = 10。
3. 复杂度分析
-
时间复杂度:O(m × n),需要遍历整个网格。
-
空间复杂度:O(m × n),需要保存二维 DP 数组。
二、一维 DP(空间优化版本)
1. 完整代码(精简注释)
class Solution {
public:
int uniquePaths(int m, int n) {
// dp[j]:当前行第 j 列的路径数
vector<int> dp(n, 1);
// 从第二行开始遍历
for (int i = 1; i < m; i++) {
// 从第二列开始更新
for (int j = 1; j < n; j++) {
// 当前路径数 = 上方路径数 + 左方路径数
dp[j] = dp[j] + dp[j-1];
}
}
// 返回右下角的路径数
return dp[n-1];
}
};
2. 动态规划五步总结
(1)确定 DP 数组及其含义
dp[j] 表示当前处理到的行中,第 j 列位置的路径数量。
注意:一维 DP 数组会随着外层循环不断更新,代表不同的行。
(2)确定递推公式
二维 DP 的递推公式:
dp[i][j] = dp[i-1][j] + dp[i][j-1];
压缩成一维后:
dp[j] = dp[j] + dp[j-1];
为什么可以这样写?
-
更新前的
dp[j]:表示上一行同一列的路径数,即上方。 -
更新后的
dp[j-1]:表示当前行左边一列的路径数,即左方。
因此,两者相加就是当前格子的路径数。
(3)DP 数组初始化
vector<int> dp(n, 1);
初始化为第一行的路径数。
因为第一行所有位置都只能一直向右移动,所以路径数都是 1。
(4)确定遍历顺序
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[j] += dp[j-1];
}
}
-
外层从第二行开始遍历。
-
内层从第二列开始,从左往右更新。
为什么必须从左往右?
因为计算当前 dp[j] 时,需要使用已经更新的 dp[j-1],它表示当前行左边位置的路径数。
(5)举例推导 DP 数组
假设:
m = 3;
n = 4;
初始化:
dp = [1, 1, 1, 1]
处理第二行:
dp[1] = 1 + 1 = 2;
dp[2] = 1 + 2 = 3;
dp[3] = 1 + 3 = 4;
得到:
dp = [1, 2, 3, 4]
处理第三行:
dp[1] = 2 + 1 = 3;
dp[2] = 3 + 3 = 6;
dp[3] = 4 + 6 = 10;
得到:
dp = [1, 3, 6, 10]
最终返回:
return dp[n-1]; // 10
3. 复杂度分析
-
时间复杂度:O(m × n),需要遍历整个网格。
-
空间复杂度:O(n),只需要保存一行的 DP 状态。
三、二维 DP 与一维 DP 对比
|
对比项 |
二维 DP |
一维 DP |
|---|---|---|
|
DP 定义 |
|
|
|
递推公式 |
|
|
|
初始化 |
|
|
|
遍历顺序 |
从上到下、从左到右 |
从上到下、从左到右 |
|
时间复杂度 |
O(m × n) |
O(m × n) |
|
空间复杂度 |
O(m × n) |
O(n) |
四、核心易错点
1. 为什么二维 DP 可以压缩成一维?
因为计算当前位置时,只需要上方和左方的路径数,不需要保存整个二维数组。
2. 为什么一维 DP 必须从左往右更新?
因为 dp[j-1] 必须是当前行已经更新过的值,才能正确表示左方路径数。
3. 为什么循环从 i=1、j=1 开始?
因为第一行和第一列都只有一条路径,已经初始化为 1,不需要再次计算。
4. 为什么二维 DP 返回 dp[m-1][n-1],一维 DP 返回 dp[n-1]?
二维 DP 保存整个网格,所以右下角对应 dp[m-1][n-1]。
一维 DP 只保存当前行,遍历到最后一行后,dp[n-1] 就是右下角的路径数。
五、最终总结
这道题的核心思想是:
当前位置的路径数 = 上方位置的路径数 + 左方位置的路径数。
二维 DP:
dp[i][j] = dp[i-1][j] + dp[i][j-1];
一维 DP:
dp[j] = dp[j] + dp[j-1];
二者的本质完全相同,只是存储状态的方式不同。
-
二维 DP:保存整个网格的路径数,更容易理解。
-
一维 DP:只保存一行的路径数,利用上一行和当前行的状态进行更新,节省空间。
记忆:先通过二维 DP 理解状态转移,再根据当前状态只依赖上方和左方的特点,将二维 DP 压缩成一维 DP。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)