动态规划入门1 - 爬楼梯问题,从递归到动态规划的完美演绎
一、题目描述
你正在爬一座楼梯。它有 n 个台阶。
每次你可以爬 1 个台阶 或 2 个台阶。
问:你有多少种不同的方法可以爬到楼顶?
二、举个例子
比如:
当
n = 1时:只有[1]一种走法当
n = 2时:有两种走法[1+1]、[2]当
n = 3时:先爬 1 阶 → 剩下 2 阶的走法有 2 种
先爬 2 阶 → 剩下 1 阶的走法有 1 种
所以总共有2 + 1 = 3种。
也就是:
f(1) = 1
f(2) = 2
f(3) = f(2) + f(1) = 3
三、递归思路:分解问题
每次你到第 n 阶,只可能是从:
第
n-1阶(再走 1 步)第
n-2阶(再走 2 步)
所以:
f(n) = f(n-1) + f(n-2)
这其实和 斐波那契数列 完全一样。
对应的递归代码:
class Solution {int dfs(int i) {if (i <= 1) return 1; // base casereturn dfs(i - 1) + dfs(i - 2);}
public:int climbStairs(int n) {return dfs(n);}
};
理解关键:
dfs(i)表示 “到第 i 阶的走法数”。如果只有 1 阶或 0 阶,走法只有 1 种。
每一步要么来自
i-1阶,要么来自i-2阶。
所以自然相加。
四、递归的缺陷:重复计算
比如求 f(5):
f(5)/ \f(4) f(3)/ \ / \f(3) f(2) f(2) f(1)
你会发现 f(3)、f(2) 被重复计算了好多次。
→ 所以我们引入 记忆化搜索 或 动态规划。
五、动态规划解法(自底向上)
class Solution {
public:int climbStairs(int n) {vector<int> f(n + 1);f[0] = f[1] = 1; // base case:0阶和1阶都只有一种走法for (int i = 2; i <= n; i++) {f[i] = f[i - 1] + f[i - 2];}return f[n];}
};
思想解释:
f[i]表示到第i阶的走法数。对于每个
i:从第
i-1阶走一步上来 → 有f[i-1]种方式;从第
i-2阶走两步上来 → 有f[i-2]种方式;两者加起来就是总走法数。
| i | f[i] | 说明 |
|---|---|---|
| 0 | 1 | 什么都不做,一种方式 |
| 1 | 1 | [1] |
| 2 | 2 | [1+1], [2] |
| 3 | 3 | [1+1+1], [1+2], [2+1] |
| 4 | 5 | [1+1+1+1], [1+1+2], [1+2+1], [2+1+1], [2+2] |
| 5 | 8 | 共 8 种走法 |
七、优化:只存两个变量
其实我们每次只用到前两个状态,可以优化为 O(1) 空间:
int climbStairs(int n) {if (n <= 1) return 1;int a = 1, b = 1;for (int i = 2; i <= n; i++) {int c = a + b;a = b;b = c;}return b;
}
八、核心总结
| 思路 | 特点 | 时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 递归 | 简单直观,但有重复计算 | O(2ⁿ) | O(n) |
| 记忆化搜索 | 缓存结果 | O(n) | O(n) |
| 动态规划 | 自底向上 | O(n) | O(n) |
| 滚动变量 | 最优 | O(n) | O(1) |
九、最后一点思考
很多人一开始会问:
为什么不是 f[i] = f[i-1] + f[i-2] + 2 呢?
其实 f[i] 记录的是「走法数」,不是「步数的和」。
那最后一步(+1 或 +2)已经包含在走法里面了,不需要再额外加。
所以这道题不仅是动态规划的入门题,
更是理解「状态表示」和「状态转移」的完美练习。
或者这么说:
从 i-1 阶走 1 步上来(这时所有能到 i-1 的走法,都可以 +1 步到 i)从 i-2 阶走 2 步上来(这时所有能到 i-2 的走法,都可以 +2 步到 i)