题目
假设你正在爬楼梯,需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶,你有多少种不同的方法可以爬到楼顶呢?
题解
方法1:基本递归
算法
基本递归即初学递归时所学的最简单、最直观的方法。
假设你爬了 n
段楼梯,则爬上当前楼梯所拥有的方法数是爬上 n-1
段楼梯的方法数加上爬上 n-2
段楼梯的方法数。
代码
class Solution {
public:
int climbStairs(int n) {
if (n == 1 || n == 2)
return n;
else
return climbStairs(n - 1) + climbStairs(n - 2);
}
};
分析
此方法最简单、直观、易懂,逻辑也很清晰。
但底层的数字被大量重复计算,很容易超时。
- 时间复杂度: O ( 2 n ) O(2^n) O(2n)
- 空间复杂度:
O
(
n
)
O(n)
O(n)
图源LeetCode官方题解
方法2:记忆化递归
算法
鉴于基本递归会大量重复计算底层数字,那么我们可以想到,不妨将计算过的数字存起来,需要的时候先看是否已经计算,算过的直接取出来,没有算过的计算,这样每个数字都只会计算一次。
代码
class Solution {
public:
int climb(int n, int mem[]) {
if (n == 1 || n == 2)
return n;
if (mem[n - 1] == 0)
mem[n - 1] = climb(n - 1, mem) + climb(n - 2, mem);
return mem[n - 1];
}
int climbStairs(int n) {
int *mem = new int[n] {0};
return climb(n, mem);
}
};
分析
通过维护一个mem数组,将算过的数字从第3个空间开始存起来,前两个空间位置本来应该放第1、2个楼梯的方法数,但不便于创建时指定,故干脆空着了。
当 mem[i - 1]
等于0,表明位置 i-1
的方法数没算出来,需要计算。最后返回 mem[i - 1]
就行了。
- 时间复杂度: O ( n ) O(n) O(n),可以满足LeetCode不超时
- 空间复杂度: O ( n ) O(n) O(n)
方法3:斐波那契循环
算法
其实从前面的解题思路可以得知,爬楼梯本质上就是斐波那契数列。故我们可以使用循环计算斐波那契。
代码
class Solution {
public:
int climbStairs(int n) {
if (n == 1 || n == 2)
return n;
int a = 1, b = 2, t;
for (int i = 0; i < n - 2; i++) {
t = b;
b += a;
a = t;
}
return b;
}
};
分析
之所以是 for (int i = 0; i < n - 2; i++)
,是因为要比n少循环2次,这个可以代入3来理解:计算3的时候只需要循环一次即可,故3减去2即得1。
该算法时间复杂度为 O(n)
,需要从前往后计算n次,空间复杂度只有 O(1)
,因为没有动态创建空间。
动态规划
算法
可以看出,这个问题得最优解可从它的子问题得最优解得出,即:
第 i
个楼梯最多方法数 = 第 i-1
个楼梯最多方法数 + 第 i-2
个楼梯最多方法数:
dp[i] = dp[i - 1] + dp[i - 2]
代码
class Solution {
public:
int climbStairs(int n) {
if (n == 1 || n == 2)
return n;
int *dp = new int[n];
dp[0] = 1;
dp[1] = 2;
for (int i = 2; i < n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n - 1];
}
};
分析
由于每一次的最优解都取决于前两次的最优解,故从前往后得出所有楼梯的最优解并存在dp数组中,大体上跟上面的斐波那契数列算法非常相似,时间复杂度与空间复杂度均为 O(n)
。