zoukankan      html  css  js  c++  java
  • 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)

  • 相关阅读:
    Github 简明教程--GitHub这么火,测试员你不学学吗?
    IT行业,尤其是软件测试,怎么才能月薪突破2万?
    linux 下cmake 编译 ,调用,调试 poco 1.6.0 小记
    ffmpeg(2.6) rockplayer android 下编译 小记.
    完成端口
    C++四种强制转换
    方法区(Method Area)基础知识
    逃逸分析
    堆空间参数设置小结
    堆中的线程私有缓存区域TLAB(Thread Local Allocation Buffer)
  • 原文地址:https://www.cnblogs.com/Howfars/p/12289919.html
Copyright © 2011-2022 走看看