假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
首先会想到暴力求解:
第一个台阶有一种走法,第二个台阶有两种走法,第三个台阶有三种走法。。。第四个台阶...第n个台阶,蒙了。。。
像这种循环的话一般是要找到一个规律,比如说像这种爬楼梯的,假设在第九阶的话,那么上到第九阶的话,其实就是从第八阶迈一步,或者从第七阶迈两步,而走到第八阶,其实就是从第七阶迈一步,或者从第6阶迈两步。这样的话,第n阶台阶,走法就是从第(n-1)个台阶迈一步,或者从(n-2)个台阶迈两步。。。一直递归下去(擦,转来转去就是个递归(斐波那契数列)呀~)
解一:
既然了解了题意,先来一波递归吧:
public static int climbStairs(int n) {
if (n == 1) {
return 1;
}
if (n == 2) {
return 2;
}
if (n > 2) {
return climbStairs(n - 1) + climbStairs(n - 2);
}
return 0;
}
很完美,来走一波:
竟然超时了。。。
用递归还是得小心呀~
解法二:
我们也可以反着推导下,之前解法一是上第n阶台阶需要(n-1)阶+(n-2)阶,反着想就是如果我要上第三阶的话,是第一阶走法+第二阶走法(第一阶走法是1,第二阶走法是2),那么上第四阶走法就是第三阶走法+第二阶走法(这时第三阶走法已经有了),第五阶走法就是第四阶走法+第三阶走法。。。第n阶走法就是第(n-1)阶走法+第(n-2)阶走法,当然我们需要一个额外的数据结构来保存之前的走法信息:
public int climbStairs(int n) {
if (n == 1) {
return 1;
}
if (n == 2) {
return 2;
}
int[] result = new int[n + 1];
result[0] = 1;
result[1] = 1;
result[2] = 2;
for(int i = 3; i<= n ;i++){
result[i] = result[i - 1] + result[i-2];
}
return result[n];
}
看下执行结果:
分析下,因为我们从0遍历到第n个台阶,所以时间复杂度为O(n),我们用额外的n长度的数组保存中间结果,所以空间复杂度为O(n)
tips:
1.像解法一这种比较原始的递归,是可以优化的,主要优化点是保存中间结果,不需要再次计算,比如第九个台阶,需要第八个台阶的走法+第七个台阶的走法,而第八个台阶的走法也需要第七个台阶的走法+第六个台阶的走法。。。我们可以用一个缓存容器将中间结果保存起来,以后计算时,先从缓存中看下是否存在(类似于解法二,用数组缓存中间结果)
2.解法二是缓存了中间结果,这是空间换时间的思想,其实很多中间结果并不一定是我们想要的,我们其实只需要关注当前阶数的前一个和前一个的前一个(pre和pre的pre),有了这两个结果就可以计算出当前的阶梯走法了,这样的话会将空间复杂度降低到O(1),can you try it?
解法二改:
public static int climbStairs3(int n) {
if (n == 1) {
return 1;
}
if (n == 2) {
return 2;
}
int preOfPre = 1, pre = 2;
int current = 0;
for (int i = 3; i <= n; i++) {
current = preOfPre + pre;
preOfPre = pre;
pre = current;
}
return current;
}
用两个指针分别记录前一个阶梯走法和前二个阶梯走法,重复利用这两个变量,使得空间复杂度降低到O(1):