🧗♂️ 题目登场:你家楼道有 n 级台阶,每次只能走 1 步或 2 步,问有多少种不同的方式能爬到顶?
这道题乍一看——“就这?”
但别急!这可是 LeetCode 第70题《Climbing Stairs》,更是无数大厂(阿里、腾讯、字节、美团...)高频面试题!
你以为只是考递归?太天真了。
面试官真正想看的,是你如何从“暴力递归”一路优化到“空间 O(1)”的思维跃迁!
今天,我们就用四种姿势,把这道题扒得底裤都不剩——顺便让你在面试中惊艳全场!
🌳 方法一:朴素递归 —— “我裂开了”
function climbStairs(n) {
if (n === 1) return 1;
if (n === 2) return 2;
return climbStairs(n - 1) + climbStairs(n - 2);
}
✅ 思路
- 自顶向下拆解:要上第
n阶,要么从n-1跨1步,要么从n-2跨2步。 - 于是:
f(n) = f(n-1) + f(n-2),妥妥的斐波那契数列!
❌ 问题在哪?
看这张调用树(以 n=5 为例):
f(5)
/ \
f(4) f(3)
/ \ / \
f(3) f(2) f(2) f(1)
/ \
f(2) f(1)
重复计算爆炸!
f(3) 算了两次,f(2) 算了三次……时间复杂度高达 O(2ⁿ) ,n=40 就卡成PPT。
💡 面试官内心OS:“小伙子,你这代码跑完,我头发都白了。”
🧠 方法二:记忆化递归 —— “我开挂了!”
const climbStairs = (function() {
const memo = {}; // 私有缓存,闭包保护
return function climb(n) {
if (n === 1) return 1;
if (n === 2) return 2;
if (memo[n]) return memo[n];
memo[n] = climb(n - 1) + climb(n - 2);
return memo[n];
};
})();
✅ 思路
- 空间换时间:用
memo缓存已计算结果,避免重复劳动。 - 利用闭包让
memo在多次调用间持久存在(不用全局变量,更优雅)。
⏱️ 复杂度
- 时间:O(n) (每个 n 只算一次)
- 空间:O(n) (递归栈 + 缓存)
✨ 面试亮点:你能主动提出“用闭包封装缓存”,说明你懂模块化和副作用控制!
🔁 方法三:动态规划(自底向上)—— “稳如老狗”
function climbStairs(n) {
if (n === 1) return 1;
if (n === 2) return 2;
const dp = new Array(n + 1);
dp[1] = 1;
dp[2] = 2;
for (let i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
✅ 思路
- 自底向上填表:先算
f(1),f(2),再一步步推到f(n)。 - 这就是经典的动态规划(DP) :状态转移方程
dp[i] = dp[i-1] + dp[i-2]。
⏱️ 复杂度
- 时间:O(n)
- 空间:O(n)
💬 面试官点头:“嗯,知道 DP 的基本套路,不错。”
🚀 方法四:滚动变量优化 —— “极致压榨内存!”
function climbStairs(n) {
if (n === 1) return 1;
if (n === 2) return 2;
let prevPrev = 1; // f(i-2)
let prev = 2; // f(i-1)
let current;
for (let i = 3; i <= n; i++) {
current = prev + prevPrev;
prevPrev = prev;
prev = current;
}
return current;
}
✅ 思路
- 观察发现:只需要前两个值,根本不需要整个数组!
- 用三个变量“滚动”更新,像贪吃蛇一样往前爬。
⏱️ 复杂度
- 时间:O(n)
- 空间:O(1) ← 这才是面试官想听的!
🎯 高光时刻:当你说出“我们可以将空间优化到常数级别”,面试官眼睛会亮!
🧩 四种方法对比总结
| 方法 | 时间复杂度 | 空间复杂度 | 是否易爆栈 | 面试推荐度 |
|---|---|---|---|---|
| 朴素递归 | O(2ⁿ) | O(n) | ✅ 是 | ⭐ |
| 记忆化递归 | O(n) | O(n) | ❌ 否 | ⭐⭐⭐ |
| 动态规划 | O(n) | O(n) | ❌ 否 | ⭐⭐⭐⭐ |
| 滚动变量 | O(n) | O(1) | ❌ 否 | ⭐⭐⭐⭐⭐ |
💡 延伸思考:这题还能怎么变?
- 如果可以一次跨 1、2、3 步呢?
→ 状态转移变成f(n) = f(n-1)+f(n-2)+f(n-3) - 某些台阶有障碍物不能踩?
→ 加个if (obstacle[i]) dp[i] = 0 - 求所有路径(而不仅是数量)?
→ 回溯算法上场!
这道题就像“算法界的 Hello World”,看似简单,实则包罗万象。
✅ 结语:别小看“简单题”
很多同学刷题只追求数量,看到“简单”就跳过。
但真正的大厂面试,往往就在“简单题”里挖深坑。
- 能不能写出清晰的递归?
- 能不能意识到重复计算?
- 能不能主动优化空间?
- 能不能讲清楚每一步的 trade-off?
这些,才是区分“背题选手”和“真·工程师”的关键。
📌 今日金句:
“不是题目太简单,而是你还没看到它的全部面貌。”