LeetCode Notes_#70 Climbing Stairs

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两种方法,个人觉得有点冷门,所以就不写了。

参考

爬楼梯题解-力扣(LeetCode)

原文地址:https://www.cnblogs.com/Howfars/p/12289919.html