4、跳台阶问题
问题描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)
该问题课可以引申为
问题1:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
问题2:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
问题3:一只青蛙一次可以跳上1级、5级、10级台阶,求该青蛙跳上一个n级的台阶总共有多少种跳法。
解析
动态规划
假设f(n)是n级台阶的跳法,首先以简单的情况来说,
如果
n=1,f(n)=1 (1);
n=2,f(n)=2 (1-1,2);
n=3,f(n)=3 (1-1-1,2-1,1-2)······
n级对应的问题的解来源于n级的上一步所在的层级,比如,对于问题1,n级可能是上一次所处的层级跳了1级或2级,所以f(n)=f(n-1)+f(n-2),以此类推:
- 对于问题1,有公式:f(n)=f(n-1)+f(n-2), n>=2,即为一个斐波那契数列,需要用动态规划逐步求出f(0),f(1)…
- 对于问题2,有公式:f(n)=f(n-1)+f(n-2)+…f(0), f(n-1)=f(n-2)+f(n-3)+…+f(0),综合起来得,f(n)=2^(n-1),不需要用动态规划,直接套公式
- 对于问题3,有公式:f(n)=f(n-1)+f(n-5)+f(n-10), n>=10,需要用动态规划,逐步求出f(0),f(1),f(2)…直到最后需要的f(n)
代码块
问题1可以看作斐波那契数列的求解问题
1、自顶向下的动态规划–递归
这是最简单的实现方法,也是最低级的做法,耗费的时间是输入规模的指数级别的。
class Solution {
public:
int jumpFloor(int number) {
if(number<=0) return 0;
if(number==1) return 1;
if(number==2) return 2;
return jumpFloor[number-1]+jumpFloor[number-2];
}
};
2、自底向上的动态规划
是最普通的动态规划方法,线性时间复杂度即可得到结果。
class Solution {
public:
int jumpFloor(int number) {
int a[number+1];
if(number<=0) return 0;
if(number==1) return 1;
if(number==2) return 2;
a[1]=1;
a[2]=2;
for(int i =3;i<=number;i++){
a[i] = a[i-1] + a[i-2];
}
return a[number];
}
};
3、备忘录方法
这也是一种自底向上的动态规划,与递归方法基本一致。唯一的区别是这里采用了一个数组来记录每个子问题的解,从而避免重复求解,将问题简化。
class Solution {
public:
int jumpFloor(int number) {
int a[number+1];
if(number<=0) return 0;
if(number==1) return a[1] = 1;
if(number==2) return a[2] = 2;
a[number] = jumpFloor[number-1]+jumpFloor[number-2];
return a[number];
}
};
注
动态规划求解的问题的一般要具有3个性质:
(1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
(2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
(3) 有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)
拓展
问题4:矩形覆盖:我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?