Leecode 0070. Climbing Stairs
70. Climbing Stairs
题目
You are climbing a stair case. It takes n steps to reach to the top.
Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
Note: Given n will be a positive integer.
Example 1:
Input: 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps
Example 2:
Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step
题目大意
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数
解题思路
- 简单的 DP,经典的爬楼梯问题。一个楼梯可以由
n-1
和n-2
的楼梯爬上来。 - 这一题求解的值就是斐波那契数列。
解法 1:递归解法(基础版)
1 |
|
解法 1 解析
递归解法直接基于问题的数学定义:
- 要到达第 n 阶楼梯,最后一步只有两种可能:
- 从第 n-1 阶爬 1 步上来
- 从第 n-2 阶爬 2 步上来
因此,爬 n 阶楼梯的方法数 = 爬 n-1 阶的方法数 + 爬 n-2 阶的方法数
- n=1 时,只有 1 种方法(爬 1 步)
- n=2 时,有 2 种方法(1+1 或 2)
这种方法虽然直观,但存在严重的效率问题,因为会重复计算大量相同的子问题。例如计算 climbStairs (5) 时,需要计算 climbStairs (4) 和 climbStairs (3),而计算 climbStairs (4) 又需要计算 climbStairs (3),导致 climbStairs (3) 被计算了两次。
解法 2:动态规划解法(优化版)
1 |
|
解法 2 解析
动态规划解法通过存储子问题的解来避免重复计算:
- 创建一个 dp 数组,其中 dp [i] 表示爬 i 阶楼梯的方法数
- 初始化 dp [1] = 1,dp [2] = 2 作为基线条件
- 对于 i 从 3 到 n,计算 dp [i] = dp [i-1] + dp [i-2]
- 最终返回 dp [n] 作为结果
这种方法将时间复杂度从递归解法的 O (2ⁿ) 优化到了 O (n),因为每个子问题只计算一次。空间复杂度为 O (n),用于存储 dp 数组。
解法 3:空间优化的动态规划
1 |
|
解法 3 解析
观察动态规划解法可以发现,计算 dp [i] 时只需要用到 dp [i-1] 和 dp [i-2] 两个值,因此不需要存储整个 dp 数组:
- 使用 first 和 second 两个变量分别表示 dp [i-2] 和 dp [i-1]
- 每次迭代计算 current = first + second(即 dp [i])
- 更新 first 和 second 的值,为下一次迭代做准备
- 最终返回 current 作为结果
这种优化将空间复杂度从 O (n) 进一步降低到了 O (1),同时保持时间复杂度为 O (n),是该问题的最优解法。
性能对比
解法类型 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|
递归解法 | O(2ⁿ) | O(n) | 实现简单,直观 | 效率极低,有大量重复计算,n 较大时无法使用 |
动态规划 | O(n) | O(n) | 效率高,避免重复计算 | 需要额外存储空间 |
空间优化 DP | O(n) | O(1) | 效率最高,空间消耗最小 | 稍微增加了代码复杂度 |
问题本质分析
爬楼梯问题本质上是一个斐波那契数列问题,只是初始条件略有不同:
- 斐波那契数列:F (1)=1, F (2)=1, F (n)=F (n-1)+F (n-2)
- 爬楼梯问题:F (1)=1, F (2)=2, F (n)=F (n-1)+F (n-2)
这揭示了一个重要的算法设计思想:很多看似不同的问题可能具有相同的数学模型。
常见错误与解决方案
- 递归解法的效率问题
递归解法在 n=40 时就会明显变慢,n=50 时几乎无法在合理时间内得出结果。
解决方案:使用动态规划或空间优化的动态规划解法。 - 边界条件处理不当
常见错误是没有正确处理 n=1 和 n=2 的情况。
解决方案:在函数开始时显式处理这些特殊情况。 - 整数溢出问题
当 n 较大时(如 n>45),结果会超过 int 类型的最大值。
解决方案:对于大 n,可以使用 long long 类型存储结果。