【力扣100题】41.爬楼梯

题目描述

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

解题思路总览

方法 思路 时间复杂度 空间复杂度 适用场景
方法一:动态规划 dp[n] = dp[n-1] + dp[n-2],滚动数组优化 O(n) O(1) 面试首选
方法二:矩阵快速幂 利用矩阵幂加速,O(log n) O(log n) O(1) n 很大时
方法三:公式法 斐波那契数列公式 O(1) O(1) 数学推导

核心原理: 爬到第 n 阶的方法数 = 爬到第 n-1 阶的方法数 + 爬到第 n-2 阶的方法数(因为最后一次可以是爬 1 阶或 2 阶)


方法一:动态规划(推荐)

思路

经典的斐波那契数列变形题:

  1. 状态定义dp[i] 表示爬到第 i 阶的方法数
  2. 状态转移dp[i] = dp[i-1] + dp[i-2]
    • 最后一次爬 1 阶:从第 n-1 阶爬上来
    • 最后一次爬 2 阶:从第 n-2 阶爬上来
  3. 初始化dp[0] = 1, dp[1] = 1(或 dp[1] = 1, dp[2] = 2
  4. 空间优化:只用三个变量滚动更新

完整代码

cpp 复制代码
class Solution {
public:
    int climbStairs(int n) {
        int p = 0, q = 0, r = 1;  // p=dp[i-2], q=dp[i-1], r=dp[i]
        for (int i = 1; i <= n; i++) {
            p = q;
            q = r;
            r = q + p;
        }
        return r;
    }
};

算法流程图

以 n = 4 为例:

复制代码
初始:p = 0, q = 0, r = 1

i = 1:
  p = q = 0
  q = r = 1
  r = q + p = 1 + 0 = 1
  dp[1] = 1

i = 2:
  p = q = 1
  q = r = 1
  r = q + p = 1 + 1 = 2
  dp[2] = 2

i = 3:
  p = q = 1
  q = r = 2
  r = q + p = 2 + 1 = 3
  dp[3] = 3

i = 4:
  p = q = 2
  q = r = 3
  r = q + p = 3 + 2 = 5
  dp[4] = 5

返回 r = 5

验证:
dp[1] = 1  → [1]
dp[2] = 2  → [1,1], [2]
dp[3] = 3  → [1,1,1], [1,2], [2,1]
dp[4] = 5  → [1,1,1,1], [1,1,2], [1,2,1], [2,1,1], [2,2]

逐行解析

cpp 复制代码
int p = 0, q = 0, r = 1;  // p=dp[i-2], q=dp[i-1], r=dp[i]
  • 三个变量滚动更新:
    • p 存储 dp[i-2]
    • q 存储 dp[i-1]
    • r 存储 dp[i]
  • 初始值 r = 1 对应 dp[0] = 1dp[1] = 1(边界情况)
cpp 复制代码
for (int i = 1; i <= n; i++) {
    p = q;
    q = r;
    r = q + p;
}
  • 循环 n 次,每次计算一个 dp 值
  • p = q:将 dp[i-1] 变为新的 dp[i-2]
  • q = r:将 dp[i] 变为新的 dp[i-1]
  • r = q + p:计算新的 dp[i+1] = dp[i] + dp[i-1]
cpp 复制代码
return r;
  • 循环结束后,r 存储的就是 dp[n]

复杂度分析

复杂度 说明
时间 O(n) 循环 n 次
空间 O(1) 只需三个变量

优点: 空间复杂度 O(1),代码简洁
缺点:


方法二:矩阵快速幂

思路

斐波那契数列可以用矩阵乘法表示,利用快速幂可以将时间复杂度降到 O(log n)。

完整代码

cpp 复制代码
class Solution {
public:
    int climbStairs(int n) {
        if (n <= 2) return n;
        
        vector<vector<long long>> mat = {{1, 1}, {1, 0}};
        vector<vector<long long>> res = matrixPower(mat, n - 2);
        
        return 2 * res[0][0] + res[0][1];
    }

private:
    vector<vector<long long>> matrixPower(vector<vector<long long>> a, int n) {
        vector<vector<long long>> result = {{1, 0}, {0, 1}};  // 单位矩阵
        while (n > 0) {
            if (n & 1) result = multiply(result, a);
            a = multiply(a, a);
            n >>= 1;
        }
        return result;
    }
    
    vector<vector<long long>> multiply(vector<vector<long long>> a, 
                                      vector<vector<long long>> b) {
        vector<vector<long long>> c(2, vector<long long>(2));
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
            }
        }
        return c;
    }
};

复杂度分析

复杂度 说明
时间 O(log n) 快速幂
空间 O(1)

两种方法对比

维度 方法一 滚动数组 方法二 矩阵快速幂
代码复杂度 极简 复杂
时间复杂度 O(n) O(log n)
空间复杂度 O(1) O(1)
面试推荐度 首选 n 极大时展示算法能力

面试追问 FAQ

问题 解答
Q1:为什么 dp[i] = dp[i-1] + dp[i-2] 要爬到第 i 阶,最后一步只能是爬 1 阶或 2 阶。如果最后爬 1 阶,那么之前已经爬到第 i-1 阶;如果最后爬 2 阶,那么之前已经爬到第 i-2 阶。所以方法数是两者之和。
Q2:为什么初始化 r = 1 r = 1 对应 dp[0] = 1dp[1] = 1。边界情况:如果 n=1,循环执行一次后直接返回 r=1,正确;如果 n=2,循环执行两次后 r=2,正确。
Q3:为什么是斐波那契数列? 爬楼梯的状态转移方程 dp[i] = dp[i-1] + dp[i-2] 和斐波那契数列的递推式完全一样,初始值略有不同(斐波那契是 0, 1, 1, 2, 3, 5...;爬楼梯是 1, 1, 2, 3, 5, 8...)。
Q4:如果可以爬 1、2、3 个台阶怎么改? dp[i] = dp[i-1] + dp[i-2] + dp[i-3],初始化 dp[0]=1, dp[1]=1, dp[2]=2
Q5:如果可以爬 m 个台阶怎么改? dp[i] = sum(dp[i-k]) for k in 1..m,空间复杂度变为 O(m)。
Q6:如何证明动态规划的正确性? 数学归纳法:假设 dp[i-1] 和 dp[i-2] 正确,则 dp[i] = dp[i-1] + dp[i-2] 正确。初始条件 dp[0]=1, dp[1]=1 正确。所以对于所有 i,dp[i] 都正确。

相关题目

题目 难度 关键点
70. 爬楼梯 简单 斐波那契数列,动态规划入门
509. 斐波那契数 简单 同样的滚动数组
746. 使用最小花费爬楼梯 简单 加权版本的爬楼梯
91. 解码方法 中等 爬楼梯变形,条件更多

总结

要点 说明
核心原理 dp[n] = dp[n-1] + dp[n-2],斐波那契数列
空间优化 三个变量 p, q, r 滚动更新
时间复杂度 O(n)
空间复杂度 O(1)

相关推荐
Pkmer1 小时前
LeetCode 上极少见的工程级滑窗实现
python·leetcode
sheeta19982 小时前
LeetCode 每日一题笔记 日期:2026.05.13 题目:1674. 使数组互补的最少操作次数
笔记·算法·leetcode
liulilittle2 小时前
TCP UCP v1.0:BBR 的非破坏性约束层
网络·c++·网络协议·tcp/ip·算法·c·通信
每天回答3个问题2 小时前
LeetCodeHot100|回溯算法、46.全排列、78.子集、17.电话号码的字母组合
算法·深度优先·回溯
YL200404263 小时前
038翻转二叉树
数据结构·leetcode
Liangwei Lin4 小时前
LeetCode 287. 寻找重复数
算法·leetcode·职场和发展
Daorigin_com4 小时前
道本科技三大系统形成的“合同—合规—法务”智能闭环!
科技·职场和发展·分类·服务发现·边缘计算·集成学习·敏捷流程
OCR_133716212754 小时前
护照OCR校验位技术解析:从算法逻辑到工程落地,筑牢证件核验安全线
人工智能·算法
Hello.Reader4 小时前
算法基础(十三)——随机算法为什么有时主动引入随机性
java·数据库·算法