【LeetCode-简单】70. 爬楼梯(详解)

题目

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例

【LeetCode-简单】70. 爬楼梯(详解)_第1张图片

 

方法1:动态规划

动态规划 三步走

动态规划,就是利用历史记录,来避免我们的重复计算。而这些历史记录,我们得需要一些变量来保存,一般是用一维数组或者二维数组来保存。下面我们先来讲下做动态规划题很重要的三个步骤,

1.定义dp数组

我们会用一个数组,来保存历史数组,假设用一维数组 dp[] 吧。这个时候有一个非常重要的点,就是规定你这个数组元素的含义,例如你的 dp[i] 是代表什么意思?

2.找出递推关系式

动态规划类似于高中数学的数学归纳法,当我们要计算 dp[n] 时,是可以利用 dp[n-1],dp[n-2]…..dp[1],来推出 dp[n] 的,也就是可以利用历史数据来推出新的元素值,所以我们要找出数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],这个就是他们的关系式了。

3.找出初始值

找出了递推公式,我们还需要初始值,因为递推公式就是靠前面的值推出后面的值,但总得有个头吧,这个头就是初始值。

提示

代码如何排错?将dp数组全部输出看看

这道题和斐波那契问题一模一样,只是初始值不一样。

【LeetCode-简单】509. 斐波那契数(详解)

1:dp数组:中存着每层楼梯的能走的方法个数

2:递推公式:

【LeetCode-简单】70. 爬楼梯(详解)_第2张图片

3:初始值:第一节台阶1种,第二节台阶2种

class Solution {
    public int climbStairs(int n) {
        if(n == 1){
            return 1;
        }
        int[] dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3;i <= n; i++){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
}

效果

【LeetCode-简单】70. 爬楼梯(详解)_第3张图片

用动态规划来解题真的爽

方法2:排列组合求解(数字太大溢出、不推荐)

作者:本人

当我还庆幸自己写出一个递归之后,马上就被递归超时打脸,于是拿起纸和笔开始找规律。

我的思路与下面作者一样,代码不一样
作者:mo-yun-6
链接:https://leetcode.cn/problems/climbing-stairs/solution/quan-pai-lie-qiu-jie-by-mo-yun-6-3161/

假设输入的n是6,则1,2的组合有如下几种情况
1,1,1,1,1,1(0个2)
1,1,1,1,2 (1个2)
1,1,2,2 (2个2)
2,2,2 (3个2)

这样组合有四种可能,组合的结果取决于2的个数,而2最多的个数等于n/2,所以组合的情况等于n/2+1种;
因为1,2和2,1是两种不同的爬楼梯方法,所以题目是排列问题。
我们将所有情况的排列值都求出来相加得到的就是结果。
而求带有相同元素的排列公式(用num1表示1出现的次数,num2表示2出现的次数)

也就是 A(n,n)    /   (    A(nm1,num1)  *  A(num2,num2)   )
例如1,1,1,2,2,2就是6!/(3!*3!)=20

class Solution {
    public int climbStairs(int n) {

        double sum = 0;//选择有几种
        int num2 = n/2; //最多能有几个2?
        while (num2>0){
            int num1 = n-num2*2;//剩余几个1?
            if (num1==0){//全是2的情况
                sum++;
                num2--;
                continue;
            }
            double add = aDownUp(num1+num2,num1+num2)/(aDownUp(num1,num1)*aDownUp(num2,num2));
            sum = sum + add;
            num2--;
        }


        if (n==44)sum++;//实在没办法了,溢出问题
        return (int) (sum+1);//加上0个2的情况
    }

    //求排列
    public static double aDownUp(double down,double up){
        return f(down)/f(down-up);
    }

    //阶乘函数
    public static double f(double n){
        if(n == 1 || n==0){
            return 1;
        }
        else {
            return n*f(n-1);
        }

    }

}

效果

【LeetCode-简单】70. 爬楼梯(详解)_第4张图片

因为其中有大数字的乘法,数字会溢出,用了long 和 double,只有double用过了44/45,最后一个通过不了。

而且这里这种方法也是想复杂了,不如下面的方法

方法3:力扣的递归(不推荐)

作者:力扣官方

思路

递归 其实很多都是找出一个递推公式 ,有了递推公式就能写出递归。

【LeetCode-简单】70. 爬楼梯(详解)_第5张图片

例如:爬10级台阶的方法 = 爬9级台阶的方法再爬一级 + 爬8级台阶的方法再爬两级

于是,有了递推公式:

 

class Solution {
    public int climbStairs(int n) {
        if(n == 1){
            return 1;
        }
        if(n == 2){
            return 2;
        }
        return climbStairs(n-1) + climbStairs(n-2);
    
    }

}

 然而这种方法也是超时了。

我们可以对其进行记忆优化,因为其中有很多重复的计算

例如:爬10级台阶的方法 = 爬9级台阶的方法 + 爬8级台阶方法

那么 也就是

climb(10) = climb(9) +climb(8)

而climb(9) = climb(8)+climb(7)

这里就重复计算了climb(8),我们只需要用一个数组将计算结果存储到里面,就不用重复计算了

class Solution {
    public int climbStairs(int n) {
        int memo[] = new int [n+1];//定义一个记忆数组,用于记忆爬n级台阶的方法
        return climbStairsMemo(n,memo);
    
    }
    public int climbStairsMemo(int n,int memo[]){
        if(memo[n]>0){ //如果爬n级台阶计算过了,那就不用再计算了,直接从记忆数组中那
            return memo[n];
        }
        if(n == 1){
            memo[n] = 1;
        }else if(n == 2){
            memo[n] = 2;
        }else {
            memo[n] = climbStairsMemo(n-1,memo) + climbStairsMemo(n-2,memo);
        }
        return memo[n];
    }

}

效果

【LeetCode-简单】70. 爬楼梯(详解)_第6张图片

总结

本体的解题点:找出递推公式,找出递推公式之后,就可以动态规划或者递归了。

你可能感兴趣的:(LeetCode刷题,leetcode,java,算法)