问题简介
题目描述
假设你正在爬楼梯。需要 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 阶
❌|✅|💡|📌 解题思路
思路一:动态规划(推荐)
这是一个经典的斐波那契数列问题。
步骤分析:
- 定义状态 :设
dp[i]表示爬到第i阶的方法数。 - 状态转移方程 :
- 要到达第
i阶,可以从第i-1阶走 1 步,或从第i-2阶走 2 步。 - 所以:
dp[i] = dp[i-1] + dp[i-2]
- 要到达第
- 初始条件 :
dp[0] = 1(站在地面也算一种方式)dp[1] = 1
- 目标 :求
dp[n]
💡 注意:此题本质是斐波那契数列的第
n+1项。
思路二:空间优化的动态规划
由于 dp[i] 只依赖前两个值,可以用两个变量代替整个数组,将空间复杂度从 O(n) 降到 O(1)。
思路三:递归 + 记忆化(备忘录)
直接递归会超时(指数时间),但加上记忆化(缓存已计算结果)可优化为 O(n) 时间。
思路四:数学公式(Binet 公式)
斐波那契数列有闭式解:
F ( n ) = f r a c p h i n − p s i n s q r t 5 , q u a d t e x t 其中 p h i = f r a c 1 + s q r t 5 2 , p s i = f r a c 1 − s q r t 5 2 F(n) = \\frac{\\phi^n - \\psi^n}{\\sqrt{5}}, \\quad \\text{其中} \\ \\phi = \\frac{1+\\sqrt{5}}{2}, \\ \\psi = \\frac{1-\\sqrt{5}}{2} F(n)=fracphin−psinsqrt5,quadtext其中phi=frac1+sqrt52,psi=frac1−sqrt52
但由于浮点精度问题,一般不推荐用于编程题。
❌|✅|💡|📌 代码实现
java
class Solution {
// 方法一:动态规划(数组)
public int climbStairs1(int n) {
if (n <= 1) return 1;
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 方法二:空间优化(推荐)
public int climbStairs(int n) {
if (n <= 1) return 1;
int prev2 = 1; // dp[0]
int prev1 = 1; // dp[1]
int curr = 0;
for (int i = 2; i <= n; i++) {
curr = prev1 + prev2;
prev2 = prev1;
prev1 = curr;
}
return curr;
}
// 方法三:记忆化递归
private Map<Integer, Integer> memo = new HashMap<>();
public int climbStairs3(int n) {
if (n <= 1) return 1;
if (memo.containsKey(n)) {
return memo.get(n);
}
int res = climbStairs3(n - 1) + climbStairs3(n - 2);
memo.put(n, res);
return res;
}
}
Go
func climbStairs(n int) int {
if n <= 1 {
return 1
}
prev2 := 1 // dp[0]
prev1 := 1 // dp[1]
curr := 0
for i := 2; i <= n; i++ {
curr = prev1 + prev2
prev2 = prev1
prev1 = curr
}
return curr
}
// 可选:带记忆化的递归版本
func climbStairsMemo(n int) int {
memo := make(map[int]int)
var dfs func(int) int
dfs = func(i int) int {
if i <= 1 {
return 1
}
if val, ok := memo[i]; ok {
return val
}
res := dfs(i-1) + dfs(i-2)
memo[i] = res
return res
}
return dfs(n)
}
❌|✅|💡|📌 示例演示
以 n = 5 为例:
| 阶数 i | dp[i] 计算过程 | 值 |
|---|---|---|
| 0 | 初始 | 1 |
| 1 | 初始 | 1 |
| 2 | dp[1] + dp[0] = 1+1 | 2 |
| 3 | dp[2] + dp[1] = 2+1 | 3 |
| 4 | dp[3] + dp[2] = 3+2 | 5 |
| 5 | dp[4] + dp[3] = 5+3 | 8 |
✅ 输出:8
所有路径(共8种):
- 1+1+1+1+1
- 1+1+1+2
- 1+1+2+1
- 1+2+1+1
- 2+1+1+1
- 1+2+2
- 2+1+2
- 2+2+1
❌|✅|💡|📌 答案有效性证明
- 数学归纳法 :
- 基础:
n=1时,1 种;n=2时,2 种,成立。 - 假设对所有
k < n成立,则n阶只能由n-1(走1步)或n-2(走2步)到达。 - 故
f(n) = f(n-1) + f(n-2),符合斐波那契递推。
- 基础:
- 因此,动态规划解法正确。
❌|✅|💡|📌 复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 动态规划(数组) | O(n) | O(n) | 存储全部 dp 值 |
| 空间优化 DP | ✅ O(n) | ✅ O(1) | 最优解 |
| 记忆化递归 | O(n) | O(n) | 递归栈 + 哈希表 |
| 纯递归(无记忆) | O(2ⁿ) | O(n) | ❌ 超时,不推荐 |
| 数学公式 | O(1) | O(1) | ⚠️ 浮点误差风险,不实用 |
❌|✅|💡|📌 问题总结
- 本题是动态规划入门经典题 ,核心在于识别出状态转移关系。
- 实质是求斐波那契数列第 n+1 项。
- 推荐使用空间优化的迭代解法,简洁高效。
- 此类"一步/两步"问题广泛出现在路径计数、硬币组合等场景中,掌握其思想可举一反三。
- 💡 关键洞察:当前状态仅依赖前两个状态 → 可压缩空间。
github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions