剑指offer-裴波那切类递归,动态规划题总结

题1:斐波那契数列

题目描述:

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。

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级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

 思路:这次用递归做

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:放苹果

题目描述:

把 M 个同样的苹果放在 N 个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?
注意:5、1、1 和 1、5、1 是同一种分法,即顺序无关。
输入描述:
输入包含多组数据。每组数据包含两个正整数 m和n(1≤m, n≤20)。
输出描述:
对应每组数据,输出一个整数k,表示有k种不同的分法。
思路:
对于这个题,分两种情况:
 
如果有空盘子,相当于我先拿出一个盘子不用,用剩下的盘子去装苹果,方法数为:dp[m][n-1]
如果没有空盘子,相当于我先给每个盘子里放一个苹果,然后再用剩下的苹果去放盘子,方法数为:dp[m-n][n],不过在在此处要注意满足m>=n,不然就会为负
对于m<n的情况,苹果比盘子数还少,空盘子是没用的,去掉空盘子,那就等同于在n个盘子里放n个苹果
 
 
 
 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 }

 

posted @ 2018-06-04 19:04  pathjh  阅读(603)  评论(0编辑  收藏  举报