动态规划爬楼梯问题
题目是这样婶的,说一个人上楼梯,一次可以上一层,也可以上两层,问上n层的台阶有多少种上法。
正常人来说,上楼梯要不就是一步一层,要不就是一步两层,或者一下一层一下两层,那会有人1433223这种上楼梯方法,搁那国服元歌呢?
言归正传,这个问题很显然从正面推是不好推的,每一次出脚都有两种可能,那么我们不妨从反面来推。当我们已经在第n层时,无论怎么走,都可以回到第一层,这样我们就可以知道,第n层回退的情况有两种,一种是回退到n-1,还可以回退到n-2。
这样我们就很容易推出一下方程:
F(n) = F(n-1) +F(n-2);
加上临界条件
F(1) =1
F(2) = 2
显而易见,这种情况下递归就完事。那么代码很简单
class Solution {
public int climbStairs(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
return climbStairs(n-1)+climbStairs(n-2);
}
}
但是呢,很遗憾,本菜鸡被鄙视了…
阿这…
于是乎,我翻到了一位人美声甜的小姐姐的讲解,顿时恍然大悟。原来我们在递归时,会涉及到大量重复的计算,导致整个程序的效率不高,甚至是O(2^n),没错,你没有看错,确实是指数爆炸。这简直就是噩梦!!!!
以五层台阶举例说明,它的调用树为
小明要上一个80层的台阶,按照本菜鸡的想法来算出一共有多少种走法的话,直到小明的孙子的孙子的孙子的孙子那一辈可能才能捧着小明的骨灰告诉他:爷爷,这个程序员脑子不太好使…
于是我选择在这个基础上进行改进,改进的策略也好理解,就是对于每次递归算过的数值,我们把它存下来,再下次调用的时候,直接拿出来用即可!
思路是这样婶的:
代码如下
class Solution {
public int climbStairs(int n) {
int memo[] = new int[n+1];
return climbStairsMemo(n,memo);
}
public int climbStairsMemo(int n,int memo[]) {
if(memo[n] > 0) {
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];
}
}
啊哈哈哈,不亏是我!
不过跑题了,回到动态规划上来说,根据以上思路,我们可以得到
F(n) = F(n-1) +F(n-2);
那么我们可以构建一个数组,来存这个斐波拉契数列就行了。
代码如下
public int climbStairs(int n) {
if(n == 1) {
return 1;
}
int[] dp = new int[n+1];
//这个地方设置从第一个位置起,是为了避免数组越界!懂的都懂,当然也可以从0开始数,毕竟程序员都是从0开始数数的。
dp[1] = 1;
dp[2] =2;
for(int i = 3;i < n+1;i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
分析一下,解法一的时间复杂度2^n,第二种n,动态规划n;
完结,撒花!
结尾夹带些私货,节选自五柳先生的闲情赋。
激清音以感余,愿接膝以交言。欲自往以结誓,惧冒礼之为愆;待凤鸟以致辞,
恐他人之我先。意惶惑而靡宁,魂须臾而九迁:
愿在衣而为领,承华首之余芳;悲罗襟之宵离,怨秋夜之未央!
愿在裳而为带,束窈窕之纤身;嗟温凉之异气,或脱故而服新!
愿在发而为泽,刷玄鬓于颓肩;悲佳人之屡沐,从白水而枯煎!
愿在眉而为黛,随瞻视以闲扬;悲脂粉之尚鲜,或取毁于华妆!
愿在莞而为席,安弱体于三秋;悲文茵之代御,方经年而见求!
愿在丝而为履,附素足以周旋;悲行止之有节,空委弃于床前!
愿在昼而为影,常依形而西东;悲高树之多荫,慨有时而不同!
愿在夜而为烛,照玉容于两楹;悲扶桑之舒光,奄灭景而藏明!
愿在竹而为扇,含凄飙于柔握;悲白露之晨零,顾襟袖以缅邈!
愿在木而为桐,作膝上之鸣琴;悲乐极以哀来,终推我而辍音!
愿在昼而为影,常依形而西东;悲高树之多荫,慨有时而不同!
愿在夜而为烛,照玉容于两楹;悲扶桑之舒光,奄灭景而藏明!
愿在竹而为扇,含凄飙于柔握;悲白露之晨零,顾襟袖以缅邈!
愿在木而为桐,作膝上之鸣琴;悲乐极以哀来,终推我而辍音!