【简单】70. 爬楼梯

0 阅读3分钟

假设你正在爬楼梯。需要 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

1. 生活案例:跳跃的小青蛙

想象你是一只小青蛙,面前有一个 nn 级的台阶。你的腿部力量比较特别:

  • 技能 A:你可以用力一跳,跳 1 级台阶。
  • 技能 B:你也可以猛地一发力,跳 2 级台阶。

你的疑惑:如果你想跳到顶层(第 nn 级),到底有多少种不同的跳法组合?

逆向思维

当你站在第 nn 级台阶上回想最后一秒:

  • 你可能是从第 n1n-1 级跳了 1 步上来的。
  • 你也可能是从第 n2n-2 级跳了 2 步上来的。
  • 除此之外,没有别的可能了!
  • 结论:跳到 nn 级的方法数 = 跳到 n1n-1 级的方法数 + 跳到 n2n-2 级的方法数。

2. 代码解析与“生活化”注释

这段代码同样采用了空间优化的策略,只记录了前两步的结果,非常高效。

JavaScript

/**
 * @param {number} n - 楼梯的总级数
 * @return {number} - 不同的爬法总数
 */
var climbStairs = function (n) {
    // 生活化解释:
    // 如果只有 1 级台阶,只有 1 种跳法;
    // 如果有 2 级台阶,有 2 种跳法(1+1 或 直接跳2级)。
    if (n <= 2) return n;

    // left 相当于 dp[i-2],最初代表第 1 级台阶的方法数
    let left = 1; 
    // right 相当于 dp[i-1],最初代表第 2 级台阶的方法数
    let right = 2; 
    // res 用来存当前这一级台阶的方法数
    let res = 0;

    // 从第 3 级台阶开始算,一直算到第 n 级
    for (let i = 3; i <= n; i++) {
        // 关键逻辑:当前台阶的方法数 = 前一级的方法数 + 前两级的方法数
        res = right + left;

        // 算出当前级后,我们要往上挪一级,更新记忆:
        // 原来的“前一级”变成了现在的“前两级”
        left = right;
        // 原来的“当前级”变成了现在的“前一级”
        right = res;
    }

    return res;
};

3. 为什么代码这样写?(数学之美)

  1. 斐波那契规律:你会发现方法数序列是:1, 2, 3, 5, 8, 13... 每一个数都是前两个数的和。
  2. 拒绝递归:代码开头注释掉的 climbStairs(n-1) + climbStairs(n-2) 是递归写法。虽然直观,但它会产生大量的重复计算(比如算 nn 时要算 n1n-1,算 n1n-1 时又要再算一遍 n2n-2)。
  3. 迭代(循环)的优势:你的代码从底向上算,每一步只做一次加法,时间复杂度是 O(n)O(n),空间复杂度是 O(1)O(1),这是这道题的最优解。

总结

这道题是 “打家劫舍” 的简化版:

  • 打家劫舍:是挑大的加(Math.max)。
  • 爬楼梯:是全都要,直接加(+)。