LeetCode Notes_#70 Climbing Stairs
Contents
题目
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
思路
1.暴力法
public int climbStairs(int n) {
climb_Stairs(0,n);
}
public int climb_Stairs(int i,int n){
if (i > n){
return 0;
}
if (i == n){
return 1;
}
return climb_Stairs(i+1,n) + climb_Stairs(i+2,n);
}
自己想了一会,感觉按照之前的那种循环遍历的方法行不通,原来是需要使用递归的方法。
- 其中i是当前已经上的阶梯数,n是输入的需要上的总阶梯数。
- 两个if语句是最后一步用的,最后一步如果刚好
i==n+1
超过了n,一定是climb_Stairs(i+2,n)
,返回0,这个不能计数;最后一步如果刚好i=n
,一定是调用了climb_Stairs(i+1,n)
,计数需要加1。 - 什么是递归呢?在这一个例子当中,为了求出
climb_Stairs(0,5)
,需要先求出climb_Stairs(1,5)
和climb_Stairs(2,5)
,为了求出climb_Stairs(1,5)
,又要先求出climb_Stairs(2,5)
和climb_Stairs(3,5)
...以此类推,就是上面的树的结构。只有计算到树的最末端,才能一步一步代回到上面,回溯到根节点。
递归与迭代的比较
- 迭代是不断地处理一个变量,每一次迭代就把这个变量更新一次(丢弃中间的结果),得到一个最终结果;
- 递归是每一轮都无法得到结果,而是每一轮得到一个新问题,直到计算到最后,从最后一个问题开始回答,一步一步回溯回去,得到最开始那个问题的答案。
提交结果:超时,所以这种解法是不行的,只能作为学习,继续看别的题解。
2.记忆化递归
在解法1里边的递归树的图解中,可以看到
- 每个节点都是(i,5)也就是说climb_Stairs(i,n)的后一个参数其实都是固定的,就是input的n
- 有一些节点是重复的,比如(2,5)(3,5)(4,5)都是出现了好几次,这里n比较小,所以重复不多,但是要是n是几十的话,那么就有很多的重复项,相当于是重复计算了,所以需要进行优化
优化方法: - 用一个数组memo[i]记录climb_Stairs(i,n)的结果,在进行递归调用之前,先判断memo数组里边是否有我需要计算的数,有的话就直接返回之前存好的;
- 如果是之前没有记录过的memo[i],就得记录一下
class Solution {
public int climbStairs(int n) {
int memo[] = new int[n + 1];
return climb_Stairs(0,n,memo);
}
private int climb_Stairs(int i,int n,int memo[]){//一个细节:这个地方函数类型其实可以写private,在类内可以相互调用即可
if(i > n) return 0;
if(i == n) return 1;
if(memo[i] != 0) return memo[i];
memo[i] = climb_Stairs(i+1,n,memo)+climb_Stairs(i+2,n,memo);
return memo[i];
}
}
提交结果:Beat 100%.
3.动态规划(Dynamic Programming,DP)
另一种思路,对于除了1,2之外的n的输入,都有以下公式成立:dp(i)=dp(i-1)+dp(i-2),因为:走到i-1之后只能选择再走1步达到i,走到i-2之后只能选择再走2步达到i。
其实到这里,也就能看出这是斐波那契数列了,可以用斐波那契数列的解法解决。
public class Solution{
public int climbStairs(int n){
if(n == 1) return 1;//ERROR:corner case处理
int dp[] = new int[n+1];//如果是n,输入2就会出现越界,仅仅是为了防止越界,其实计算只需要计算到dp[n]就够了
dp[1] = 1;
dp[2] = 2;
for(int i = 3;i <= n;i++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
}
提交结果:beat 100%
4.斐波那契数列
由方法3观察dp数组的规律可以得到,其实爬到n阶台阶的方法数就是以n作为下标的斐波那契数列。
那么问题转化为一个计算斐波那契数列的第n项。
class Solution {
public int climbStairs(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
return climbStairs(n-1) + climbStairs(n-2);
}
}
简单的想法是直接使用递归,这样写提交结果是时间超过限制。当输入参数n比较大的时候,按照递归的方法,需要回溯到n==1,n==2
的情况,所以会速度很慢。
解决方法是“以空间换时间”,维护3个变量,first
,second
,third
,并且不断更新,这样就免去了重复的计算过程。
class Solution {
public int climbStairs(int n) {
if(n == 1) return 1;//corner case
int first = 1;
int second = 2;
for(int i = 3;i <= n;i++){//第一个third是dp[3]
int third = first + second;
first = second;
second = third;//更新
}
return second;//最后climeStairs(n)就是更新之后的second
}
}
分析:其实方法4和方法3几乎是一样的,区别是方法4只使用了3个变量,空间复杂度为O(1),而方法3需要使用n个变量,空间复杂度为O(n)。
官方题解中还有5,6两种方法,个人觉得有点冷门,所以就不写了。