在算法面试中,爬楼梯问题(LeetCode 第 70 题)是一道经典的动态规划入门题。本文将带你从零开始,用 JavaScript 实现一个高效、易读的解法,并深入探讨其背后的斐波那契数列思想与工程落地细节。无论你是准备面试还是巩固基础,这篇文章都能给你清晰的思路和可复用的代码模板。

问题理解与模式识别

题目描述很简单:你正在爬楼梯,每次可以走 1 步或 2 步,问到达第 n 阶有多少种不同的方法?例如,n = 3 时,有 (1+1+1)、(1+2)、(2+1) 三种走法。

这个问题的本质是 斐波那契数列变种。到达第 i 阶的方法数 F(i) 等于 F(i-1)(再走 1 步)加上 F(i-2)(再走 2 步)。边界条件:F(1) = 1F(2) = 2

在 JavaScript 中,我们通常用函数形式实现,如 F(n) = F(n-1) + F(n-2)F(1)=1F(2)=2

⚙️ 算法选择与工程落地

对于爬楼梯问题,最直接的解法是 动态规划(迭代法),时间复杂度 O(n),空间复杂度 O(1)。为什么不用递归?因为递归会导致大量重复计算,而迭代法只需两个变量即可完成。

JavaScript 工程落地考虑:

  • 函数定义:通常使用 function climbStairs(n) 或箭头函数。LeetCode 平台可能提供类结构,但多数情况下就是普通函数。
  • 整数范围:JavaScript 的 number 类型是双精度浮点数,其安全整数范围(Number.MAX_SAFE_INTEGER)远超第 45 阶斐波那契数,所以 number 类型足够。
  • 变量声明:使用 let 声明可变变量,const 声明不可变变量。
  • 边界处理n=1, n=2
  • 注释:采用 JSDoc 风格(/** ... */),提升可维护性。

✅ 这种设计思路同样适用于 TypeScriptPythonJava 等语言,只需调整类型声明和语法即可。

编写 JavaScript 代码

下面给出完整的实现,包含详细的 JSDoc 注释和边界处理:

/**
 * @param {number} n 楼梯总阶数 (1 <= n <= 45)。
 * @return {number} 到达楼顶的不同方法数。
 *
 * @description
 * 思路:该问题是一个经典的斐波那契数列变种。
 * 到达第 i 阶的方法数,等于到达第 i-1 阶(再走1步)与到达第 i-2 阶(再走2步)的方法数之和。
 * 令 F(i) 为到达第 i 阶的方法数。
 * 递推关系:F(i) = F(i-1) + F(i-2)。
 *
 * 边界条件:
 * F(1) = 1 (只可能走 1 步)
 * F(2) = 2 (可能走 1+1 或 2)
 *
 * 采用动态规划(迭代法)实现,空间复杂度为 O(1),时间复杂度为 O(n)。
 * JavaScript 的 number 类型足以处理 F(45) 的结果。
 */
var climbStairs = function(n) {
    // 处理基本边界情况
    if (n === 1) {
        return 1;
    }
    if (n === 2) {
        return 2;
    }
    // 使用 O(1) 空间复杂度的动态规划
    // prev2 存储 F(i-2) 的值,prev1 存储 F(i-1) 的值
    let prev2 = 1;  // 对应 F(1)
    let prev1 = 2;  // 对应 F(2)
    // 从第 3 阶开始迭代计算,直到第 n 阶
    // 循环执行 n - 2 次
    for (let i = 3; i <= n; i++) {
        // 计算当前阶数的方法数
        const currentWays = prev1 + prev2;
        // 更新 prev2 和 prev1,为下一轮迭代做准备
        prev2 = prev1;          // 原来的 F(i-1) 变为下一轮的 F(i-2)
        prev1 = currentWays;    // 新计算出的 F(i) 变为下一轮的 F(i-1)
    }
    // 循环结束后,prev1 中存储的就是 F(n) 的值
    return prev1;
};

测试与代码审查

让我们测试几个关键用例:

  • 示例 1n = 2n === 2 返回 2。✅ 正确。
  • 示例 2n = 3prev2 = 1, prev1 = 2, i = 3: currentWays = 2 + 1 = 3. prev2 = 2, prev1 = 3. 循环结束,返回 prev1 (3)。✅ 正确。
  • 边界情况n = 1 已在代码中处理。

代码风格审查:

  • 函数定义使用 var climbStairs = function(n) { ... };function climbStairs(n) { ... } 结构。LeetCode 提供了 var climbStairs = function(n) { ... }; 的模板。
  • 变量命名 prev2, prev1, currentWays 清晰明了。
  • JSDoc 注释(/** ... */)包含 @param, @return, @description,是 JavaScript 的标准实践。
  • letconst 的使用:let 用于变量 prev1, prev2(会改变),const 用于 currentWays(在循环内部计算一次,不改变)。
  • ===:使用严格相等比较,避免隐式类型转换,是 JS 的良好实践。

⚠️ 如果你使用 TypeScript,可以给参数和返回值加上类型注解,进一步提升代码健壮性。例如 function climbStairs(n: number): number

最终检查与总结

代码逻辑正确,通过所有示例。变量类型 number 足够存储结果。最终提交的代码模板如下:

/**
 * @param {number} n 楼梯总阶数 (1 <= n <= 45)。
 * @return {number} 到达楼顶的不同方法数。
 *
 * @description
 * 思路:该问题是一个经典的斐波那契数列变种。
 * 到达第 i 阶的方法数,等于到达第 i-1 阶(再走1步)与到达第 i-2 阶(再走2步)的方法数之和。
 * 令 F(i) 为到达第 i 阶的方法数。
 * 递推关系:F(i) = F(i-1) + F(i-2)。
 *
 * 边界条件:
 * F(1) = 1 (只可能走 1 步)
 * F(2) = 2 (可能走 1+1 或 2)
 *
 * 采用动态规划(迭代法)实现,空间复杂度为 O(1),时间复杂度为 O(n)。
 * JavaScript 的 number 类型足以处理 F(45) 的结果。
 */
var climbStairs = function(n) {
    // 处理基本边界情况
    if (n === 1) {
        return 1;
    }
    if (n === 2) {
        return 2;
    }
    // 使用 O(1) 空间复杂度的动态规划
    // prev2 存储 F(i-2) 的值,prev1 存储 F(i-1) 的值
    let prev2 = 1;  // 对应 F(1)
    let prev1 = 2;  // 对应 F(2)
    // 从第 3 阶开始迭代计算,直到第 n 阶
    // 循环执行 n - 2 次
    for (let i = 3; i <= n; i++) {
        // 计算当前阶数的方法数
        const currentWays = prev1 + prev2;
        // 更新 prev2 和 prev1,为下一轮迭代做准备
        prev2 = prev1;          // 原来的 F(i-1) 变为下一轮的 F(i-2)
        prev1 = currentWays;    // 新计算出的 F(i) 变为下一轮的 F(i-1)
    }
    // 循环结束后,prev1 中存储的就是 F(n) 的值
    return prev1;
};

JavaScript 版快闪赛的特点总结:

  • 函数定义:使用 var functionName = function(params) {...}function functionName(params) {...} 结构。
  • 变量声明:letconst 的使用,强调了变量的可变性。
  • JSDoc 注释:符合 JavaScript 标准的注释格式,提高代码可读性和可维护性。
  • 严格相等 ===:比 == 更安全,避免隐式类型转换。

如果你对动态规划感兴趣,还可以尝试用 PythonC++ 实现同样的逻辑,对比不同语言的语法差异。

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。