爬楼梯?不,你在攀登算法的珠穆朗玛峰!
一道看似"幼儿园难度"的面试题:
"每次能爬1阶或2阶,问爬到第n阶有几种方法?"却暗藏递归、动态规划、记忆化、空间优化四大内功心法------
它不是考你会不会算数,而是看你有没有系统性思维。
🧗♂️ 初见:天真递归 ------ "我能行!"(然后爆栈了)
最直觉的解法?当然是递归!
scss
function climbStairs(n) {
if (n === 1) return 1;
if (n === 2) return 2;
return climbStairs(n - 1) + climbStairs(n - 2);
}
逻辑完美:
- 要到第
n阶,要么从n-1上来,要么从n-2跳上来 - 所以
f(n) = f(n-1) + f(n-2)------ 这不就是斐波那契?
但问题来了:
当你调用 climbStairs(45),电脑会疯狂重复计算:
f(43)被算两次f(42)被算三次- ......
时间复杂度 O(2ⁿ) ------ 指数爆炸!
就像你让一个人背完整本字典来查一个词------可行,但荒谬。
🧠 进阶:记忆化递归 ------ "我记住了!"
既然重复计算是罪魁祸首,那就把算过的答案存起来!
ini
const memo = {};
function climbStairs(n) {
if (n === 1) return 1;
if (n === 2) return 2;
if (memo[n]) return memo[n]; // ← 关键:查缓存!
memo[n] = climbStairs(n - 1) + climbStairs(n - 2);
return memo[n];
}
✅ 效果 :每个 f(k) 只算一次 → 时间复杂度 O(n)
✅ 思想 :空间换时间 ,典型的自顶向下动态规划(Top-down DP)
但有个小瑕疵:memo 是全局变量,容易被污染。
🔒 优雅封装:闭包 + 记忆化 ------ "我的缓存,外人别碰!"
用闭包 把 memo 私有化,打造一个"智能函数":
ini
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];
};
})();
✨ 优势:
- 多次调用共享缓存(越用越快)
- 状态私有,安全可靠
- 接口干净:用户只需
climbStairs(n)
这不是函数,这是一个会学习、有记忆、懂封装的智能体。
🚀 终极优化:自底向上 + 滚动变量 ------ "我不需要递归!"
其实,我们根本不需要递归,也不需要存所有中间值!
观察规律:
scss
f(1) = 1
f(2) = 2
f(3) = f(2) + f(1) = 3
f(4) = f(3) + f(2) = 5
...
只需要两个变量,就能滚动计算出结果:
ini
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)
for (let i = 3; i <= n; i++) {
const current = prev + prevPrev; // f(i)
prevPrev = prev; // 滚动窗口
prev = current;
}
return prev;
}
✅ 时间复杂度 :O(n)
✅ 空间复杂度 :O(1) ------ 极致优化!
✅ 无递归:避免调用栈溢出(n 很大时更安全)
这就是自底向上的动态规划(Bottom-up DP) ------ 从已知出发,一步步推导未知。
📊 四种解法对比
| 方法 | 时间复杂度 | 空间复杂度 | 是否递归 | 适用场景 |
|---|---|---|---|---|
| 暴力递归 | O(2ⁿ) | O(n) | ✅ | 教学演示 |
| 记忆化递归 | O(n) | O(n) | ✅ | 中等规模,逻辑清晰 |
| 闭包记忆化 | O(n) | O(n) | ✅ | 需要缓存复用 |
| 滚动变量 | O(n) | O(1) | ❌ | 生产环境首选 |
💡 面试加分回答
当面试官问这道题,你可以这样说:
"我会根据场景选择方案:
- 如果是教学或快速原型,用记忆化递归,逻辑直观;
- 如果是高性能生产环境,用滚动变量的迭代法,O(1) 空间且无栈溢出风险。
此外,我还会考虑边界情况(如 n ≤ 0)、类型校验,以及是否需要支持'每次可爬1~k阶'的扩展。"
------瞬间从"会写代码"升级到"有工程思维"。
🌟 结语:小题大智慧
"爬楼梯"从来不是一道数学题,而是一面镜子:
- 它照出你是否理解递归的本质
- 它检验你是否掌握动态规划的思想
- 它考验你能否在简洁、性能、可维护性之间做权衡
下次再有人说"这题太简单",你可以微笑回应:
"是啊,简单到能写出四种境界。"
而这,正是优秀工程师和普通 coder 的分水岭。