剑指offer-裴波那切类递归,动态规划题总结
题1:斐波那契数列
题目描述:
n<=39
思路:这类题一般都是递归,带备忘的递归,动态规划这几种做法
1 public class Solution { 2 public int Fibonacci(int n) { 3 if(n==0)return 0; 4 if(n==1||n==2)return 1; 5 int pre=1; 6 int cur=1; 7 int next=0; 8 for(int i=3;i<=n;i++){ 9 next=cur+pre; 10 pre=cur; 11 cur=next; 12 } 13 return next; 14 } 15 }
题2:跳台阶
题目描述:
思路:这次用递归做
1 public class Solution { 2 public int JumpFloor(int target) { 3 if(target<=1)return 1; 4 if(target==2)return 2; 5 return JumpFloor(target-1)+JumpFloor(target-2); 6 } 7 }
跳台阶的同类题:
上台阶:
有一楼梯共m级,刚开始时你在第一级,若每次只能跨上一级或者二级,要走上m级,共有多少走法?注:规定从一级到一级有0种走法。
给定一个正整数int n,请返回一个数,代表上楼的方式数。保证n小于等于100。为了防止溢出,请返回结果Mod 1000000007的值。
3
返回:2
思路:这次用动态规划和矩阵快速幂,思路见我代码里的注释
1 import java.util.*; 2 /* 3 典型的裴波那切问题,用递归或是循环都可解决,主要是要找到递推关系式 4 5 设x为要跳过的级数,f(x)为方式数 6 当当前跳1级时,剩下的级数还有f(x-1)种 7 当当前跳2级时,剩下的级数还有f(x-2)种 8 所以总的方式有:f(x)=f(x-1)+f(x-2); 9 利用此递推式便可求出总的方式数 10 若采用矩阵快速幂法可先构造出初始矩阵 11 |1 1| |f(x-1)| |f(x) | 12 |1 0| * |f(x-2)| = |f(x-1)| 13 由 14 |1 1| |f(1)| |f(2)| 15 |1 0| * |f(0)| = |f(1)| 16 |1 1| |f(2)| |f(3)| 17 |1 0| * |f(1)| = |f(2)| 18 .... 19 所以可得 20 {|1 1|}^(x-1) |f(1)| |f(x) | 21 {|1 0|} * |f(0)| = |f(x-1)| 22 */ 23 public class GoUpstairs { 24 /* 25 public int countWays(int n) { 26 // 用动态规划 27 long dp[]=new long[n+1]; 28 int m=1000000007; 29 dp[0]=0; 30 dp[1]=0; 31 dp[2]=1; 32 dp[3]=2; 33 for(int i=4;i<=n;i++){ 34 dp[i]=(dp[i-1]+dp[i-2])%m; 35 } 36 37 return (int)dp[n]; 38 39 } 40 */ 41 public int countWays(int n) { 42 //用矩阵快速幂 43 long[][] base={{1,1},{1,0}}; 44 long[][] unit={{1,1},{1,0}}; 45 46 /* 47 while(n!=0){ 48 if(n%2==1) 49 unit= matrixMultiple(unit,base); 50 else{ 51 base= matrixMultiple(base,base); 52 } 53 n=n/2; 54 } 55 */ 56 if(n<=1)return 0; 57 for(int i=1;i<n-1;i++){ 58 base=matrixMultiple(base,unit); 59 } 60 return (int)(base[0][0]); 61 } 62 public long[][] matrixMultiple(long[][] ret,long[][] base){ 63 //这个函数实质上就是实现ret与base的矩阵相乘 64 long [][]tmp=new long [2][2]; 65 for(int i=0;i<2;i++){ 66 for(int j=0;j<2;j++){ 67 tmp[i][j]=(ret[i][0]*base[0][j]+ret[i][1]*base[1][j])%1000000007; 68 } 69 } 70 for(int i=0;i<2;i++){ 71 for(int j=0;j<2;j++){ 72 ret[i][j]=tmp[i][j];} 73 } 74 return ret; 75 76 } 77 }
题3;变态跳台阶
题目描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路:最后一节阶梯是必跳的,其他的都有跳或不跳两种情况,所以总的方法数就是2^(n-1)
1 public class Solution { 2 public int JumpFloorII(int target) { 3 if(target==0)return 1; 4 return 1<<--target; 5 } 6 }
题4:放苹果
题目描述:
注意:5、1、1 和 1、5、1 是同一种分法,即顺序无关。
1 import java.util.*; 2 public class Main{ 3 private static final int maxn=25; 4 public static void main(String[] args){ 5 int [][]dp=new int[maxn][maxn]; 6 for(int i=0;i<maxn;i++){ 7 dp[i][1]=1; 8 dp[0][i]=1; 9 dp[1][i]=1; 10 } 11 for(int i=1;i<maxn;i++){ 12 for(int j=2;j<maxn;j++){ 13 if(i>=j) 14 dp[i][j]=dp[i][j-1]+dp[i-j][j]; 15 else if(i<j) 16 dp[i][j]=dp[i][i]; 17 18 19 } 20 } 21 22 Scanner sc=new Scanner(System.in); 23 while(sc.hasNext()){ 24 int m=sc.nextInt(); 25 int n=sc.nextInt(); 26 int res=0; 27 28 System.out.println(dp[m][n]); 29 } 30 } 31 }
整数划分:求将一个整数m至多划分成n个数有多少种情况
变形:求将一个整数m划分成n个数有多少种情况
dp[m][n] = dp[m-n][n] + dp[m-
1
][n-
1
]; 对于变形后的问题,存在两种情况:
1
. n 份中不包含
1
的分法,为保证每份都 >=
2
,可以先拿出 n 个
1
分到每一份,然后再把剩下的 m- n 分成 n 份即可,分法有: dp[m-n][n]
2
. n 份中至少有一份为
1
的分法,可以先那出一个
1
作为单独的
1
份,剩下的 m-
1
再分成 n-
1
份即可,分法有:dp[m-
1
][n-
1
]
题5:换零钱
题目描述:
有一个数组changes,changes中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,对于一个给定值x,请设计一个高效算法,计算组成这个值的方案数。
给定一个int数组changes,代表所以零钱,同时给定它的大小n,另外给定一个正整数x,请返回组成x的方案数,保证n小于等于100且x小于等于10000。
[5,10,25,1],4,15
返回:6
[5,10,25,1],4,0
返回:1
思路:
这个题与之前做过的上楼梯,整数划分,篮子放苹果等的同属同一类题
对于上楼梯类题型,对于n层楼梯,上的方式较少,大多只有2,3种,比如一次上1层,或是一次上2层
则可以直接得到f(n)=f(n-1)+f(n-2);
使用带备忘的递归,或是自底向上的dp都比较好解决
对于整数划分类题型,将一个整数n分成m个数相加的形式,求有多少种分法
直接对所分出的数中是否含有1,可得到2种情况
即dp[n][m]=dp[n][m-1]+dp[n-m][m]
对于此题,给定金额就好像是上面中待分的整数,只不过此时由于金额大小的限制,该整数只能由给定数组中的数组成
1 import java.util.*; 2 3 public class Exchange { 4 public int countWays(int[] changes, int n, int x) { 5 // write code here 6 int [][] dp=new int[x+1][n]; 7 //dp[i][j]表示的是对于金额为i的,所给的钱的大小为change[0~j]中的数时,所对应的方案数 8 for(int i=0;i*changes[0]<=x;i++){ 9 //第一列表示用大小为change[0]的钱去组成金额为i的方案数, 10 //可知,只有当金额为change[0]的倍数时,才存在有一种方案数 11 dp[i*changes[0]][0]=1; 12 } 13 for(int j=0;j<n;j++){ 14 //第一行表示金额为0时,各个钱数的组成方案,可知都只有一种方案 15 dp[0][j]=1; 16 } 17 for(int i=1;i<=x;i++){ 18 for(int j=1;j<n;j++){ 19 dp[i][j]=dp[i][j-1]+(i-changes[j]>=0?dp[i-changes[j]][j]:0); 20 } 21 } 22 return dp[x][n-1]; 23 } 24 }