假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
动态规划 三步走
动态规划,就是利用历史记录,来避免我们的重复计算。而这些历史记录,我们得需要一些变量来保存,一般是用一维数组或者二维数组来保存。下面我们先来讲下做动态规划题很重要的三个步骤,
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:递推公式:
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];
}
}
效果
用动态规划来解题真的爽
作者:本人
当我还庆幸自己写出一个递归之后,马上就被递归超时打脸,于是拿起纸和笔开始找规律。
我的思路与下面作者一样,代码不一样
作者: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);
}
}
}
效果
因为其中有大数字的乘法,数字会溢出,用了long 和 double,只有double用过了44/45,最后一个通过不了。
而且这里这种方法也是想复杂了,不如下面的方法
作者:力扣官方
思路
递归 其实很多都是找出一个递推公式 ,有了递推公式就能写出递归。
例如:爬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];
}
}
效果
本体的解题点:找出递推公式,找出递推公式之后,就可以动态规划或者递归了。