题目
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
提示:
1 <= n <= 45
解法1:记忆化搜索
假如需要到达第n阶楼梯时,可以在n-1阶时爬1阶,或者n-2阶爬2阶到达。那么递推公式很容易得到,即f(n) = f(n - 1) + f(n - 2);
递归终止条件有两个,一个是n = 1时,f(1) = 1,因为只有一种方式到第一个台阶,另外一个是n = 2时,f(2) = 2,因为有两种方式到第二个台阶。
但是,很容易发现在该递归处理中有很多重复子问题,时间复杂度会达到O(2^n),时间复杂度极高。为了避免相同子问题的重复计算,需要对子问题的答案进行保存。在每次求解时,先判断是否存在该子问题答案,如果存在则直接使用,否则对子问题进行求解处理。
代码如下:
class Solution {
public:
int climbStairs(int n) {
if (result_[n] == 0) {
result_[n] = climbStairs(n - 1) + climbStairs(n - 2);
}
return result_[n];
}
private:
// 由于当前题目的台阶范围为[1, 45], 因此初始化长度为46的数组, 除开索引为1和2的初始值分别为1和2外,其余值均为0
array<int, 46> result_ = {{0, 1, 2}};
};
时间复杂度为O(n),空间复杂度为O(1),因为以46固定长度分配数组空间。执行结果如下:
解法2:动态规划
记忆化搜素属于自上而下的问题解决方式,本题也可使用自下而上的动态规划方式先对各个子问题求解, 最终得到问题的答案。
代码如下:
class Solution {
public:
int climbStairs(int n) {
// 由于当前题目的台阶范围为[1, 45], 因此初始化长度为46的数组, 除开索引为1和2的初始值分别为1和2外,其余值均为0
array<int, 46> result = {{0, 1, 2}};
for (int i = 3; i <= n; ++i) {
result[i] = result[i - 1] + result[i - 2];
}
return result[n];
}
};
时间复杂度为O(n),空间复杂度为O(1),固定长度分配。执行结果如下:
解法3:内存优化后的动态规划
在解法2中,固定分配了长度为46的数组,但是针对本题,无需保存那么多子问题的解,通过递推公式发现只需保存前两个子问题的解即可。代码如下:
class Solution {
public:
int climbStairs(int n) {
if (n == 1) {
return 1;
}
if (n == 2) {
return 2;
}
int pre_result = 2;
int pre_two_result = 1;
int result = 0;
for (int i = 3; i <= n; ++i) {
result = pre_result + pre_two_result;
pre_two_result = pre_result; // 先更新前两次子问题的结果
pre_result = result; // 再更新前一次子问题的结果
}
return result;
}
};
时间复杂度为O(n),空间复杂度为O(1)。执行结果如下: