假设你正在爬楼梯。需要 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
我的答案:
一、信息
这个问题是关于计算到达楼顶的不同方法的数目。每次可以爬1或2个台阶,需要确定总共有多少种不同的方法来爬到楼顶。
二、分析
问题的本质
问题的核心在于,对于到达某个台阶级数 n
,有多少种不同的方式可以达到这个级数。由于每次移动可以是1个或2个台阶,因此到达第 n
个台阶的方式可以由以下两种情况之一得到:
- 从第
n-1
个台阶爬上一个台阶。 - 从第
n-2
个台阶爬上两个台阶。
动态规划的转移方程
基于上述理解,可以得出动态规划的转移方程:f(n) = f(n-1) + f(n-2)
。这意味着到达第 n
级台阶的方式总数等于到达第 n-1
级台阶的方式数加上到达第 n-2
级台阶的方式数。
边界条件
为了开始这个递推,需要定义边界条件,即最基本的情况:
f(0) = 1
:没有台阶时的方式数为1(即不移动)。f(1) = 1
:到达第1个台阶只有一种方式,即爬一个台阶。
代码实现
在代码实现中,使用三个变量 p
, q
, 和 r
来存储 f(n-2)
, f(n-1)
, 和 f(n)
的值。这些变量随着每次迭代更新,最终得到 f(n)
。
理解滚动数组
所谓“滚动数组”,是一种优化空间复杂度的方法。在这个问题中,只需要存储最近两个状态的值,而不是整个状态序列。这样可以将空间复杂度从 O(n)
降低到 O(1)
。
总的来说,这个问题展示了动态规划在解决计数类问题上的有效性,尤其是在处理具有重叠子问题和最优子结构特征的情况。通过递推关系式和边界条件的设定,可以有效地解决这类问题。
这是一个典型的动态规划问题。对于给定的台阶数 �n,可以看作是前两个台阶数的方法数之和。具体来说:
- 如果只有1个台阶,显然只有1种方法(爬1个台阶)。
- 如果有2个台阶,有2种方法(爬1个台阶两次,或直接爬2个台阶)。
- 对于 �>2n>2 的情况,可以把问题分解为两个子问题:到达第 �−1n−1 个台阶的方法数和到达第 �−2n−2 个台阶的方法数。所以,到达第 �n 个台阶的方法数就是这两个数的和。
三、算法设计
- 初始化两个变量,表示到达前两个台阶的方法数(对应 �=1n=1 和 �=2n=2)。
- 使用一个循环从3到 �n,在每一步中更新方法数。
- 返回第 �n 个台阶的方法数。
四、代码实现(C++)
#include <iostream>
using namespace std;
class Solution {
public:
int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
};
int main() {
Solution solution;
int n = 3; // 可以修改这里的 n 来测试其他台阶数
cout << "Number of ways to climb " << n << " steps: " << solution.climbStairs(n) << endl;
return 0;
}
五、实现代码过程中可能遇到的问题
- 初始条件的处理:必须正确处理 �=1n=1 和 �=2n=2 的情况。
- 整型溢出:对于较大的 �n(接近45),可能会导致整型溢出。可以使用更大的数据类型(如
long long
)来避免这个问题。 - 效率问题:如果使用递归而不是动态规划,可能会导致重复计算,从而降低效率。在这个问题中,使用动态规划是最优解法。
Leetcode官方题解:
方法一、动态规划法
class Solution { public: int climbStairs(int n) { int p = 0, q = 0, r = 1; for (int i = 1; i <= n; ++i) { p = q; q = r; r = p + q; } return r; } };
学到了什么?
-
动态规划(Dynamic Programming):
- 概念理解:动态规划是解决具有重叠子问题和最优子结构特性的问题的一种方法。它将大问题分解为小问题,解决小问题,并使用这些解决方案来构建大问题的解决方案。
- 应用:在这个问题中,我们用动态规划来计算到达每个台阶的方法数,其中每一步的结果都建立在前一步的结果之上。
-
最优子结构(Optimal Substructure):
- 概念理解:一个问题的最优解包含其子问题的最优解。在这里,达到第
n
阶的最优解(方法数)是由达到第n-1
和n-2
阶的最优解组合而成的。 - 应用:爬楼梯问题明确展示了如何通过组合子问题的解来构造整体问题的解。
- 概念理解:一个问题的最优解包含其子问题的最优解。在这里,达到第
-
重叠子问题(Overlapping Subproblems):
- 概念理解:在递归算法中,相同的子问题多次出现。
- 应用:在计算不同台阶的方法数时,较高台阶的解依赖于较低台阶的解,这些较低台阶的解是重叠的子问题。
-
空间复杂度优化:
- 概念理解:通过使用最小化的内存来存储必要的信息,可以提高算法的空间效率。
- 应用:使用滚动数组(这里的三个变量
p
,q
,r
)来避免存储整个状态数组,将空间复杂度从O(n)
降低到O(1)
。
-
编程实践:
- 代码结构:了解如何在特定环境(如 LeetCode)中结构化代码,以符合平台的测试框架。
- 问题分解:学习如何将一个看似复杂的问题分解为更小、更易管理的部分。
-
数学建模能力:
- 从实际问题到数学模型:将实际问题(如爬楼梯)转化为数学模型(这里是斐波那契数列),并找到解决方案。
-
递归思维与迭代实现:
- 尽管问题可以用递归方式表达,但迭代实现更有效,显示了递归和迭代之间的选择重要性。