📌 题目链接:70. 爬楼梯 - 力扣(LeetCode)
🔍 难度:简单 | 🏷️ 标签:动态规划、数学、斐波那契数列
⏱️ 目标时间复杂度:O(n)(基础解法),可优化至 O(log n)
💾 空间复杂度:O(1)
🧩 题目分析
题目描述:
假设你正在爬楼梯。需要n阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶?
这是一个经典的组合计数问题,表面是“爬楼梯”,实则隐藏着斐波那契数列的结构。
观察示例:
n = 2→[1+1, 2]→ 2 种n = 3→[1+1+1, 1+2, 2+1]→ 3 种
你会发现:第 n 阶的方法数 = 第 n-1 阶 + 第 n-2 阶。
因为最后一步要么走 1 步(来自 n-1),要么走 2 步(来自 n-2)。
这正是斐波那契递推关系!
💡 面试考点:
- 能否识别出动态规划模型?
- 是否知道这是斐波那契数列的变体?
- 能否从 O(n) 优化到 O(log n)?
- 是否了解通项公式与精度问题?
🧠 核心算法及代码讲解
✅ 方法一:动态规划(滚动数组优化)
我们定义 f(n) 表示爬到第 n 阶的方法数。
状态转移方程:
f(n) = f(n-1) + f(n-2)
边界条件:
f(0) = 1(站在地面,算一种方式)f(1) = 1(只能走 1 步)
由于 f(n) 只依赖前两项,我们无需开数组,只需三个变量滚动更新:
// 核心算法:动态规划 + 滚动数组
int climbStairs(int n) {
int p = 0, q = 0, r = 1; // p = f(i-2), q = f(i-1), r = f(i)
for (int i = 1; i <= n; ++i) {
p = q; // 更新 f(i-2)
q = r; // 更新 f(i-1)
r = p + q; // f(i) = f(i-1) + f(i-2)
}
return r; // 返回 f(n)
}
🔍 为什么初始化
r = 1?
因为i=1时,r = f(1) = 1。而p和q初始为 0,对应f(-1)=0,f(0)=1的逻辑(实际我们让q在第一次循环后变成 1)。
⚡ 方法二:矩阵快速幂(进阶)
当 n 极大(如 1e18)时,O(n) 不够快。我们可以用矩阵快速幂将时间复杂度降至 O(log n) 。
由递推式: $$ \begin{bmatrix} f(n+1) \ f(n) \end{bmatrix}
\begin{bmatrix}
1 & 1 \
1 & 0
\end{bmatrix}
\cdot
\begin{bmatrix}
f(n) \
f(n-1)
\end{bmatrix}
因此,f(n) = M^{n}[0][0](因为 f(1)=1, f(0)=1,且 M^n * [1,1]^T 的第一个元素就是 f(n+1),但注意官方解法中直接返回 res[0][0] 对应 f(n),需对齐初始条件)。
✅ 实际上,若定义
f(0)=1, f(1)=1,则f(n) = M^n[0][0]成立。
📐 方法三:通项公式(数学解法)
斐波那契数列通项(Binet 公式):
但由于浮点精度问题,仅适用于较小的 n(如 n ≤ 45) ,否则会因舍入误差出错。
🧭 解题思路(分步)
-
理解问题:每次走 1 或 2 步,求总方案数。
-
找规律:手动计算
n=1,2,3,4→ 发现1,2,3,5...是斐波那契数列。 -
建模:设
f(n)为到第 n 阶的方法数 →f(n) = f(n-1) + f(n-2)。 -
确定边界:
f(0)=1,f(1)=1。 -
选择算法:
- 基础:迭代 + 滚动数组(O(n) 时间,O(1) 空间)
- 进阶:矩阵快速幂(O(log n))
- 数学:通项公式(慎用)
-
编码实现:优先使用滚动数组,简洁高效。
📊 算法分析
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 递归(无记忆) | O(2ⁿ) | O(n) | ❌ 不可用 |
| 记忆化搜索 / DP 数组 | O(n) | O(n) | 小 n |
| 滚动数组 DP | O(n) | O(1) | ✅ 推荐(n ≤ 45) |
| 矩阵快速幂 | O(log n) | O(1) | ✅ 超大 n(如 1e18) |
| 通项公式 | O(1)(假设 pow 为 O(1)) | O(1) | ⚠️ 有精度风险 |
💬 面试建议:
- 先写出 O(n) 滚动数组解法(清晰、无 bug)
- 再讨论能否优化到 O(log n),展示知识广度
- 提及通项公式但指出其局限性,体现严谨性
💻 代码
C++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 方法一:动态规划(滚动数组)
class Solution {
public:
int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
};
// 方法二:矩阵快速幂(可选实现)
class SolutionMatrix {
public:
vector<vector<ll>> multiply(vector<vector<ll>>& a, vector<vector<ll>>& b) {
vector<vector<ll>> c(2, vector<ll>(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;
}
vector<vector<ll>> matrixPow(vector<vector<ll>> a, int n) {
vector<vector<ll>> ret = {{1, 0}, {0, 1}}; // 单位矩阵
while (n > 0) {
if ((n & 1) == 1) {
ret = multiply(ret, a);
}
n >>= 1;
a = multiply(a, a);
}
return ret;
}
int climbStairs(int n) {
vector<vector<ll>> base = {{1, 1}, {1, 0}};
vector<vector<ll>> res = matrixPow(base, n);
return (int)res[0][0];
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
Solution sol;
// 测试用例
cout << sol.climbStairs(2) << "\n"; // 输出: 2
cout << sol.climbStairs(3) << "\n"; // 输出: 3
cout << sol.climbStairs(5) << "\n"; // 输出: 8
return 0;
}
JS
// JavaScript 版本(滚动数组)
var climbStairs = function(n) {
let p = 0, q = 0, r = 1;
for (let i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
};
// 测试
console.log(climbStairs(2)); // 2
console.log(climbStairs(3)); // 3
console.log(climbStairs(5)); // 8
// JavaScript 版本(矩阵快速幂)
const multiply = (a, b) => {
const c = new Array(2).fill(0).map(() => new Array(2).fill(0));
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 2; j++) {
c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
}
}
return c;
}
const matrixPow = (a, n) => {
let ret = [[1, 0], [0, 1]];
while (n > 0) {
if ((n & 1) === 1) {
ret = multiply(ret, a);
}
n >>= 1;
a = multiply(a, a);
}
return ret;
}
var climbStairsMatrix = function(n) {
const base = [[1, 1], [1, 0]];
const res = matrixPow(base, n);
return res[0][0];
};
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!