💥 一道“爬楼梯”题,居然藏着大厂面试的4种解法?别小看它!

80 阅读4分钟

🧗‍♂️ 题目登场:你家楼道有 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. 如果可以一次跨 1、2、3 步呢?
    → 状态转移变成 f(n) = f(n-1)+f(n-2)+f(n-3)
  2. 某些台阶有障碍物不能踩?
    → 加个 if (obstacle[i]) dp[i] = 0
  3. 求所有路径(而不仅是数量)?
    → 回溯算法上场!

这道题就像“算法界的 Hello World”,看似简单,实则包罗万象。


✅ 结语:别小看“简单题”

很多同学刷题只追求数量,看到“简单”就跳过。
真正的大厂面试,往往就在“简单题”里挖深坑

  • 能不能写出清晰的递归?
  • 能不能意识到重复计算?
  • 能不能主动优化空间?
  • 能不能讲清楚每一步的 trade-off?

这些,才是区分“背题选手”和“真·工程师”的关键。


📌 今日金句
“不是题目太简单,而是你还没看到它的全部面貌。”