0 题目
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
1 解题分析
动态规划的组成部分:
- (1)确定状态
- 划归为最后一步看问题。
- 划归为子问题
本题中以青蛙跳台阶的最后一步来看:青蛙跳上n级台阶有两种情况,要么青蛙跳一步,要么青蛙跳两步。
1)当为1时即青蛙跳一步的时候:此时青蛙前面已经跳了n-1个台阶,有f(n-1)种跳法。
2)当为2时即青蛙跳二步的时候:此时青蛙前面已经跳了n-2个台阶,有f(n-2)种跳法。
- (2)转移方程
由于青蛙跳最后一步是确定的状态,那么他跳n个台阶的时候,取决于前n-1个台阶的跳法及前n-2个台阶的跳法
因此青蛙跳n个台阶的跳法有f(n)=f(n-1)+f(n-2)种跳法。转移方程即为:f(n)=f(n-1)+f(n-2)
- (3) 确定初始条件及边界情况
当台阶为0时, 即f(0)=1(题中规定)
当台阶为1时,青蛙只有一种跳法,f(1)=1
当台阶为2时青蛙有2种跳法,f(2)=2
- (4)确定计算顺序
f(0),f(1),f(2)。。。。。。。
综上动态规划算法步骤如下:
(1)定义dp为一维数组,其中dp[i]值代表青蛙第i个台阶时,所需要的步数
(2)dp[i+1] =dp[i]+dp[i-1]
(3)初始状态:dp[0]=0,dp[1]=1,即初始化前两个数字
(4)返回值dp[n]
代码如下:
class Solution {
public int fib(int n) {
if(n == 0) return 1;
//开一个dp数组,数组长度n+1
int[] dp = new int[n + 1];
//dp初始化
dp[0] = 1;
dp[1] = 1;
//从第二个开始循环迭代
for(int i = 2; i <= n; i++){
//状态转移方程
dp[i] = dp[i-1] + dp[i-2];
dp[i] %= 1000000007;//防止int类型溢出
}
return dp[n];
}
}
求余运算规则: 设正整数 x, y, px,y,p ,求余符号为⊙ ,则有(x+y)⊙p=(x⊙p+y⊙p)⊙p 。
解析: 根据以上规则,可推出f(n)⊙p=[f(n−1)⊙p+f(n−2)⊙p]⊙p。从而可以在循环过程中每次计算sum=(a+b)⊙1000000007 ,此操作与最终返回前取余等价。
取余原因
“int32类型是十位数,对1e9取模可防止int32溢出”、“1e9+7是质数,对质数取模可以尽可能地让模数避免相等”以及“1e9+7是离1e9最近的质数,比较好记”
节约空间的迭代:
在动态规划过程中发现,每次用到的仅仅是前两次的值。因此可以用两个变量保存每次计算后的值,并更新他。
public int numWays(int n) {
if(n==1 ||n==0){
return 1;
}
//定义a,b来保存每次计算后的结果,a为小数,b为大数据
int a = 1;
int b = 1;
//c来保存a+b的结果
int c = 0;
for(int i =2; i<=n;i++){
c =(a +b)%1000000007;
a = b ;
b = c ;
}
return c;
}
从计算效率、空间复杂度上看,节约空间的迭代是本题的最佳解法。
【2】有一楼梯共m级,刚开始时你在第一级,若每次只能跨上一级或者二级,要走上m级,共有多少走法?注:规定从一级到一级有0种走法。
给定一个正整数int n,请返回一个数,代表上楼的方式数。保证n小于等于100。为了防止溢出,请返回结果Mod 1000000007的值。
JD笔试题,这里从1级到1级算0种。且初始你在第一级。
public class GoUpstairs {
public int countWays(int n) {
// write code here
int[] dp=new int[n+1];
dp[0]=0;
dp[1]=0;
dp[2]=1;
dp[3]=2;
for(int i=4;i<n+1;i++){
dp[i]=(dp[i-1]+dp[i-2])%1000000007;
}
return dp[n];
}
注意这里与青蛙跳台阶略有不同的是边界及初始值的确定不同,其他都一样。
【3】变态跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
考察点:数学建模能力,公式推导及规律总结。
动态规划
我们设跳到一级有F(1)种方法,跳到二级有F(2)种方法.....F(3)...F(4).....
由题意,我们从每一级台阶都能跳到第n级,所以
F(n)=F(n-1)+F(n-2)+F(n-3).......F(1)
而 F(n-1)=F(n-2)+F(n-3)+F(n-4).......F(1)
相减得F(n)-F(n-1)=F(n-1) ====> F(n)=2*F(n-1);(数学归纳法)
于是只要保留1项结果就可以了,再分析初始值,
f(1)=1;
f(2)=2;……
于是f(n)=2^(n-1);
class Solution {
public:
int jumpFloorII(int number) {
vector<int> result(number + 1);
result[0] = 1;
result[1] = 1;
result[2] = 2;
for (int i = 3; i <= number; i++)
result[i] = 2 * result[i - 1];//动态规划的条件方程
return result[number];//包含了以上的情况
}
};
由递推关系可知 F(n) = 2 * F(n-1),用一个变量来记录每次计算产生的F(n-1)的值,直到循环到n。
public int frog(int target){
if(target==0||target==1)
return 1;
if(target==2)
return 2;
int sum=1;
for(int i=1;i<target;i++){
sum *= 2;
}
return sum;
}
常识:一个整数除以2可以使用向右位移1位来实现,即2>>1=1;一个整数乘以2可以使用向左位移1位来实现,4<<1=8,于是本题中f(n)=1<<(n-1),注意左移动-1并不等价于右移1,于是对n=0,n=1特殊考虑。
publicclass Solution {
public int JumpFloorII(int target) {
//特殊输入:对于target为0的情况OJ并不关心
if(target<0) return 0;
if(target==1) return 1;
return 1<<(target-1);
}
}
2 小结
解题套路:
- (1)确定状态:划归为最后一步
- (2)确定转移方程
- (3)确定初始状态及边界
- (4)确定计算顺序