题目分析
题目要求在一个 m × n 的网格中,从 左上角 走到 右下角,只能 向右 或 向下 走,计算 不同的路径数。
解法:动态规划
1. 定义状态
设 f[i][j] 为 到达网格 (i, j) 的不同路径数。
2. 状态转移方程
一个点 (i, j) 只能来自:
• 上方 (i-1, j),即 f[i-1][j]
• 左方 (i, j-1),即 f[i][j-1]
因此,状态转移方程:
3. 初始状态
• 第一行 只能从左侧走过来,因此 f[0][j] = 1。
• 第一列 只能从上方走过来,因此 f[i][0] = 1。
示例 1
输入
m = 3, n = 3
网格表示:
(S) → →
↓ ? ?
↓ ? (E)
(S) 是起点,(E) 是终点。
初始化 DP 数组
开始时 f 表:
1 1 1
1 0 0
1 0 0
第一行 全部填 1(只能从左侧走来),第一列 全部填 1(只能从上方走来)。
详细计算过程
Step 1: 计算 f[1][1]
f[1][1] = f[0][1] + f[1][0] = 1 + 1 = 2
更新 f 表:
1 1 1
1 2 0
1 0 0
Step 2: 计算 f[1][2]
f[1][2] = f[0][2] + f[1][1] = 1 + 2 = 3
更新 f 表:
1 1 1
1 2 3
1 0 0
Step 3: 计算 f[2][1]
f[2][1] = f[1][1] + f[2][0] = 2 + 1 = 3
更新 f 表:
1 1 1
1 2 3
1 3 0
Step 4: 计算 f[2][2]
f[2][2] = f[1][2] + f[2][1] = 3 + 3 = 6
更新 f 表:
1 1 1
1 2 3
1 3 6
最终结果
f[m-1][n-1] = f[2][2] = 6
路径数 6 是如何来的?
6 种可能路径:
1. 右 → 右 → 下 → 下
2. 右 → 下 → 右 → 下
3. 右 → 下 → 下 → 右
4. 下 → 右 → 右 → 下
5. 下 → 右 → 下 → 右
6. 下 → 下 → 右 → 右
代码
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> f(m, vector<int>(n));
// 初始化第一行
for (int i = 0; i < m; ++i) {
f[i][0] = 1;
}
// 初始化第一列
for (int j = 0; j < n; ++j) {
f[0][j] = 1;
}
// 状态转移
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
return f[m - 1][n - 1];
}
};
时间 & 空间复杂度
• 时间复杂度:O(m × n)(遍历整个 f 数组)
• 空间复杂度:
• O(m × n)(使用 f 数组)
• 可以优化为 O(n)(仅使用一维数组)
优化方案:使用 O(n) 空间
只用一维数组 dp[j] 代替 f[i][j]:
class Solution {
public:
int uniquePaths(int m, int n) {
vector<int> dp(n, 1); // 仅存一行
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
dp[j] += dp[j - 1]; // 状态转移
}
}
return dp[n - 1];
}
};
空间优化后的思路
1. 只用 dp[j] 记录 上一行的状态,每次更新当前行。
2. dp[j] = dp[j] + dp[j-1] 表示 来自上方和左方的路径之和。
时间复杂度:O(m × n)
空间复杂度:O(n)(只存一行)
总结
1. 动态规划解法
• 定义 f[i][j] 为 到 (i,j) 的路径数
• 递推关系:f[i][j] = f[i-1][j] + f[i][j-1]
• 初始化:第一行 & 第一列全部设为 1
2. 两种代码实现
• 二维数组 f[m][n](O(m × n) 空间)
• 一维数组 dp[n](O(n) 空间)
3. 示例推导
• 计算 f 表,得 f[m-1][n-1] 作为答案
• 6 种不同路径(枚举)