【每日算法】LeetCode 70. 爬楼梯:从递归到动态规划的思维演进

对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

LeetCode 70. 爬楼梯:从递归到动态规划的思维演进

1. 题目描述

1.1 问题定义

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。问:你有多少种不同的方法可以爬到楼顶?

注意 :给定 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阶

2. 问题分析

2.1 问题本质识别

这是一个经典的 计数问题 ,属于动态规划 的入门题目。核心是计算到达第 n 阶台阶的方法总数。

2.2 关键观察

  • 要到达第 n 阶,只能从第 n-1 阶爬1阶上来,或者从第 n-2 阶爬2阶上来
  • 因此,到达第 n 阶的方法数 = 到达第 n-1 阶的方法数 + 到达第 n-2 阶的方法数
  • 这形成了斐波那契数列 关系:f(n) = f(n-1) + f(n-2)
  • 边界条件:
    • f(1) = 1 (只有1种方式:爬1阶)
    • f(2) = 2 (2种方式:1+1或2)

3. 解题思路

3.1 思路一:暴力递归法

直接根据递推公式 f(n) = f(n-1) + f(n-2) 递归求解。

时间复杂度 :O(2^n),指数级增长,会超时
空间复杂度:O(n),递归调用栈深度

3.2 思路二:记忆化递归(自顶向下)

使用数组或Map存储已计算的结果,避免重复计算。

时间复杂度 :O(n)
空间复杂度:O(n)

3.3 思路三:动态规划(自底向上)

使用数组迭代计算,从基础情况逐步推导到目标值。

时间复杂度 :O(n)
空间复杂度:O(n)

3.4 思路四:优化空间的动态规划

由于当前状态只依赖前两个状态,可以使用两个变量滚动更新。

时间复杂度 :O(n)
空间复杂度 :O(1) → 最优解

3.5 思路五:矩阵快速幂法(进阶)

使用矩阵乘法将问题转化为矩阵幂运算,利用快速幂算法加速。

时间复杂度 :O(log n)
空间复杂度:O(1)

4. 各思路代码实现

4.1 暴力递归法(不推荐,仅作教学展示)

javascript 复制代码
// 方法1:暴力递归
function climbStairsRecursive(n) {
    if (n <= 2) return n;
    return climbStairsRecursive(n - 1) + climbStairsRecursive(n - 2);
}

4.2 记忆化递归

javascript 复制代码
// 方法2:记忆化递归
function climbStairsMemo(n) {
    const memo = new Array(n + 1).fill(-1);
    
    function dfs(step) {
        if (step <= 2) return step;
        if (memo[step] !== -1) return memo[step];
        
        memo[step] = dfs(step - 1) + dfs(step - 2);
        return memo[step];
    }
    
    return dfs(n);
}

4.3 动态规划(数组)

javascript 复制代码
// 方法3:动态规划(基础版)
function climbStairsDP(n) {
    if (n <= 2) return n;
    
    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];
}

// ES6简洁写法
function climbStairsDP2(n) {
    const dp = [0, 1, 2];
    for (let i = 3; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

4.4 优化空间的动态规划(最优解)

javascript 复制代码
// 方法4:优化空间的动态规划(最优解)
function climbStairsOptimal(n) {
    if (n <= 2) return n;
    
    let prev1 = 1;  // f(i-2)
    let prev2 = 2;  // f(i-1)
    
    for (let i = 3; i <= n; i++) {
        const current = prev1 + prev2;
        prev1 = prev2;
        prev2 = current;
    }
    
    return prev2;
}

// 更优雅的写法(ES6解构赋值)
function climbStairsOptimal2(n) {
    if (n <= 2) return n;
    
    let [prev1, prev2] = [1, 2];
    
    for (let i = 3; i <= n; i++) {
        [prev1, prev2] = [prev2, prev1 + prev2];
    }
    
    return prev2;
}

4.5 矩阵快速幂法(进阶)

javascript 复制代码
// 方法5:矩阵快速幂法(进阶)
function climbStairsMatrix(n) {
    if (n <= 2) return n;
    
    // 定义矩阵乘法
    function multiply(a, b) {
        return [
            a[0] * b[0] + a[1] * b[2],
            a[0] * b[1] + a[1] * b[3],
            a[2] * b[0] + a[3] * b[2],
            a[2] * b[1] + a[3] * b[3]
        ];
    }
    
    // 矩阵快速幂
    function matrixPower(matrix, power) {
        let result = [1, 0, 0, 1]; // 单位矩阵
        let base = matrix;
        
        while (power > 0) {
            if (power & 1) {
                result = multiply(result, base);
            }
            base = multiply(base, base);
            power >>= 1;
        }
        
        return result;
    }
    
    // 转移矩阵 [[1,1],[1,0]]
    const matrix = [1, 1, 1, 0];
    const resultMatrix = matrixPower(matrix, n - 1);
    
    // f(n) = resultMatrix[0] * f(1) + resultMatrix[1] * f(0)
    // 其中 f(0)=1, f(1)=1
    return resultMatrix[0] * 1 + resultMatrix[1] * 1;
}

5. 各实现思路的复杂度、优缺点对比

方法 时间复杂度 空间复杂度 优点 缺点 适用场景
暴力递归 O(2ⁿ) O(n) 代码简洁,直观 效率极低,重复计算多 仅适合教学展示,n≤30
记忆化递归 O(n) O(n) 比暴力递归高效,保持递归思维 递归栈可能溢出,n较大时有问题 中等规模问题(n≤1000)
动态规划(数组) O(n) O(n) 思路清晰,易于理解 空间使用可以优化 教学和一般应用
优化空间DP O(n) O(1) 空间最优,效率高 状态转移不够直观 生产环境首选
矩阵快速幂 O(log n) O(1) 理论最优时间复杂度 实现复杂,常数因子大 n极大时(>10⁷)

6. 总结

6.1 核心收获

  1. 问题识别能力:识别出这是斐波那契数列问题,理解状态转移方程
  2. 优化思维演进:从暴力解 → 记忆化 → 动态规划 → 空间优化 → 数学优化
  3. 前端工程思维:空间优化在实际项目中非常重要,减少内存占用

6.2 在前端开发中的实际应用场景

场景1:路由跳转动画
javascript 复制代码
// 实现多步骤表单的过渡动画,类似爬楼梯问题
function calculateStepTransitions(steps) {
    // 类似爬楼梯,计算从第一步到最后一步的动画路径组合
    const dp = new Array(steps + 1).fill(0);
    dp[0] = 1; // 起始点
    dp[1] = 1;
    
    for (let i = 2; i <= steps; i++) {
        dp[i] = dp[i - 1] + dp[i - 2]; // 可以一次跳1步或2步
    }
    
    return dp[steps]; // 可能的过渡方式总数
}
场景2:动态表单验证
javascript 复制代码
// 多步骤表单验证,每次验证1个或2个字段
class FormValidator {
    constructor(fields) {
        this.fields = fields;
    }
    
    // 计算所有可能的验证顺序
    getValidationOrders() {
        const n = this.fields.length;
        const dp = new Array(n + 1);
        dp[0] = 1; // 没有字段
        dp[1] = 1; // 1个字段只有1种验证顺序
        
        for (let i = 2; i <= n; i++) {
            // 每次可以验证1个字段或同时验证2个关联字段
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        
        return dp[n];
    }
}
场景3:UI组件状态管理
javascript 复制代码
// 组件状态转移,类似状态机
class ComponentStateManager {
    constructor() {
        this.states = ['idle', 'loading', 'success', 'error'];
    }
    
    // 计算从初始状态到目标状态的可能路径
    calculateStatePaths(targetStateIndex) {
        const dp = new Array(targetStateIndex + 1).fill(0);
        dp[0] = 1; // 起始状态
        
        for (let i = 1; i <= targetStateIndex; i++) {
            dp[i] = dp[i - 1]; // 从前一个状态转移
            if (i >= 2) {
                dp[i] += dp[i - 2]; // 跳过中间状态
            }
        }
        
        return dp[targetStateIndex];
    }
}
相关推荐
一起养小猫2 小时前
LeetCode100天Day2-验证回文串与接雨水
java·leetcode
YoungHong19922 小时前
面试经典150题[073]:从中序与后序遍历序列构造二叉树(LeetCode 106)
leetcode·面试·职场和发展
最晚的py2 小时前
聚类的评估方法
人工智能·算法·机器学习
业精于勤的牙2 小时前
浅谈:算法中的斐波那契数(五)
算法·leetcode·职场和发展
液态不合群2 小时前
查找算法详解
java·数据结构·算法
代码游侠2 小时前
学习笔记——Linux进程间通信(IPC)
linux·运维·笔记·学习·算法
高洁012 小时前
DNN案例一步步构建深层神经网络(3)
python·深度学习·算法·机器学习·transformer
_dindong2 小时前
笔试强训:Week -8
开发语言·c++·算法
regon2 小时前
第九章 述职11 交叉面试
面试·职场和发展·《打造卓越团队》