题目描述
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
提示:
- 1 <= n <= 45
解题思路总览
| 方法 | 思路 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|---|
| 方法一:动态规划 | dp[n] = dp[n-1] + dp[n-2],滚动数组优化 | O(n) | O(1) | 面试首选 |
| 方法二:矩阵快速幂 | 利用矩阵幂加速,O(log n) | O(log n) | O(1) | n 很大时 |
| 方法三:公式法 | 斐波那契数列公式 | O(1) | O(1) | 数学推导 |
核心原理: 爬到第 n 阶的方法数 = 爬到第 n-1 阶的方法数 + 爬到第 n-2 阶的方法数(因为最后一次可以是爬 1 阶或 2 阶)
方法一:动态规划(推荐)
思路
经典的斐波那契数列变形题:
- 状态定义 :
dp[i]表示爬到第 i 阶的方法数 - 状态转移 :
dp[i] = dp[i-1] + dp[i-2]- 最后一次爬 1 阶:从第 n-1 阶爬上来
- 最后一次爬 2 阶:从第 n-2 阶爬上来
- 初始化 :
dp[0] = 1, dp[1] = 1(或dp[1] = 1, dp[2] = 2) - 空间优化:只用三个变量滚动更新
完整代码
cpp
class Solution {
public:
int climbStairs(int n) {
int p = 0, q = 0, r = 1; // p=dp[i-2], q=dp[i-1], r=dp[i]
for (int i = 1; i <= n; i++) {
p = q;
q = r;
r = q + p;
}
return r;
}
};
算法流程图
以 n = 4 为例:
初始:p = 0, q = 0, r = 1
i = 1:
p = q = 0
q = r = 1
r = q + p = 1 + 0 = 1
dp[1] = 1
i = 2:
p = q = 1
q = r = 1
r = q + p = 1 + 1 = 2
dp[2] = 2
i = 3:
p = q = 1
q = r = 2
r = q + p = 2 + 1 = 3
dp[3] = 3
i = 4:
p = q = 2
q = r = 3
r = q + p = 3 + 2 = 5
dp[4] = 5
返回 r = 5
验证:
dp[1] = 1 → [1]
dp[2] = 2 → [1,1], [2]
dp[3] = 3 → [1,1,1], [1,2], [2,1]
dp[4] = 5 → [1,1,1,1], [1,1,2], [1,2,1], [2,1,1], [2,2]
逐行解析
cpp
int p = 0, q = 0, r = 1; // p=dp[i-2], q=dp[i-1], r=dp[i]
- 三个变量滚动更新:
p存储dp[i-2]q存储dp[i-1]r存储dp[i]
- 初始值
r = 1对应dp[0] = 1或dp[1] = 1(边界情况)
cpp
for (int i = 1; i <= n; i++) {
p = q;
q = r;
r = q + p;
}
- 循环 n 次,每次计算一个 dp 值
p = q:将dp[i-1]变为新的dp[i-2]q = r:将dp[i]变为新的dp[i-1]r = q + p:计算新的dp[i+1] = dp[i] + dp[i-1]
cpp
return r;
- 循环结束后,
r存储的就是dp[n]
复杂度分析
| 复杂度 | 值 | 说明 |
|---|---|---|
| 时间 | O(n) | 循环 n 次 |
| 空间 | O(1) | 只需三个变量 |
优点: 空间复杂度 O(1),代码简洁
缺点: 无
方法二:矩阵快速幂
思路
斐波那契数列可以用矩阵乘法表示,利用快速幂可以将时间复杂度降到 O(log n)。
完整代码
cpp
class Solution {
public:
int climbStairs(int n) {
if (n <= 2) return n;
vector<vector<long long>> mat = {{1, 1}, {1, 0}};
vector<vector<long long>> res = matrixPower(mat, n - 2);
return 2 * res[0][0] + res[0][1];
}
private:
vector<vector<long long>> matrixPower(vector<vector<long long>> a, int n) {
vector<vector<long long>> result = {{1, 0}, {0, 1}}; // 单位矩阵
while (n > 0) {
if (n & 1) result = multiply(result, a);
a = multiply(a, a);
n >>= 1;
}
return result;
}
vector<vector<long long>> multiply(vector<vector<long long>> a,
vector<vector<long long>> b) {
vector<vector<long long>> c(2, vector<long long>(2));
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
}
}
return c;
}
};
复杂度分析
| 复杂度 | 值 | 说明 |
|---|---|---|
| 时间 | O(log n) | 快速幂 |
| 空间 | O(1) |
两种方法对比
| 维度 | 方法一 滚动数组 | 方法二 矩阵快速幂 |
|---|---|---|
| 代码复杂度 | 极简 | 复杂 |
| 时间复杂度 | O(n) | O(log n) |
| 空间复杂度 | O(1) | O(1) |
| 面试推荐度 | 首选 | n 极大时展示算法能力 |
面试追问 FAQ
| 问题 | 解答 |
|---|---|
Q1:为什么 dp[i] = dp[i-1] + dp[i-2]? |
要爬到第 i 阶,最后一步只能是爬 1 阶或 2 阶。如果最后爬 1 阶,那么之前已经爬到第 i-1 阶;如果最后爬 2 阶,那么之前已经爬到第 i-2 阶。所以方法数是两者之和。 |
Q2:为什么初始化 r = 1? |
r = 1 对应 dp[0] = 1 或 dp[1] = 1。边界情况:如果 n=1,循环执行一次后直接返回 r=1,正确;如果 n=2,循环执行两次后 r=2,正确。 |
| Q3:为什么是斐波那契数列? | 爬楼梯的状态转移方程 dp[i] = dp[i-1] + dp[i-2] 和斐波那契数列的递推式完全一样,初始值略有不同(斐波那契是 0, 1, 1, 2, 3, 5...;爬楼梯是 1, 1, 2, 3, 5, 8...)。 |
| Q4:如果可以爬 1、2、3 个台阶怎么改? | dp[i] = dp[i-1] + dp[i-2] + dp[i-3],初始化 dp[0]=1, dp[1]=1, dp[2]=2。 |
| Q5:如果可以爬 m 个台阶怎么改? | dp[i] = sum(dp[i-k]) for k in 1..m,空间复杂度变为 O(m)。 |
| Q6:如何证明动态规划的正确性? | 数学归纳法:假设 dp[i-1] 和 dp[i-2] 正确,则 dp[i] = dp[i-1] + dp[i-2] 正确。初始条件 dp[0]=1, dp[1]=1 正确。所以对于所有 i,dp[i] 都正确。 |
相关题目
| 题目 | 难度 | 关键点 |
|---|---|---|
| 70. 爬楼梯 | 简单 | 斐波那契数列,动态规划入门 |
| 509. 斐波那契数 | 简单 | 同样的滚动数组 |
| 746. 使用最小花费爬楼梯 | 简单 | 加权版本的爬楼梯 |
| 91. 解码方法 | 中等 | 爬楼梯变形,条件更多 |
总结
| 要点 | 说明 |
|---|---|
| 核心原理 | dp[n] = dp[n-1] + dp[n-2],斐波那契数列 |
| 空间优化 | 三个变量 p, q, r 滚动更新 |
| 时间复杂度 | O(n) |
| 空间复杂度 | O(1) |