动态规划学习

1.斐波那契数(简单)

  • 阶段划分:按照数字的位置划分为0~n
  • 状态和状态变量:使用一维数组result,result[i]代表对应的斐波那契数
  • 状态转移方程:result[i] = result[i-1] +result[i-2]
  • 边界条件:位置为0和1上的是肯定的,不用计算的
public static int solutionFibonacci(int n){
	if(n==0){
		return 0;
	}else if(n == 1){
		return 1;
	}else {
		int result[] = new int[n+1];
		result[0] = 0;
		result[1] = 1;
		for(int i=2;i<=n ; i++){
		result[i] = result[i-1] +result[i-2];
		}
	return result[n] ;
	}
}

2.数组最大不连续递增子序列(简单)

arr[] = {3,1,4,1,5,9,2,6,5}的最长递增子序列长度为4。即为:1,4,5,9

  • 阶段划分:按照数组的元素个数递增划分为0~n
  • 状态和状态变量:一维数组temp[n]代表第n个元素为止最大序列,temp[i]和temp[j]分别表示当前最大序列和i之前的最大序列
  • 状态转移方程:temp[i] = temp[j]+1
  • 边界条件:temp[i]为它前面的比它小的数中对应的temp最大值+1,所以边界为a[i]>a[j]
    例:
    arr[] = {3,1,4,1,5,9,2,6,5}
    temp[]=
public static int MaxChildArrayOrder(int a[]) {
		int n = a.length;
		int temp[] = new int[n];//temp[i]代表0...i上最长递增子序列
		for(int i=0;i<n;i++){
			temp[i] = 1;//初始值都为1
		}
		for(int i=1;i<n;i++){//外循环是阶段的增加
			for(int j=0;j<i;j++){//内循环和前面的所有数比较
				if(a[i]>a[j]){
					//如果有a[i]比它前面所有的数都大,则temp[i]为它前面的比它小的数的那一个temp+1取得的最大值
					temp[i] = temp[j]+1;
				}
			}
		}
		int max = temp[0];
		//从temp数组里取出最大的值
		for(int i=1;i<n;i++){
			if(temp[i]>max){
				max = temp[i];
			}
		}
		return max;
	}

3.数组最大连续子序列和(简单)

arr[] = {6,-1,3,-4,-6,9,2,-2,5}的最大连续子序列和为14。即为:9,2,-2,5

  • 阶段划分:按照数字的位置划分为0~n
  • 状态和状态变量:a[i]表示元素,sum表示当前元素为止所有元素和
  • 状态转移方程:(1) sum = Math.max(sum+a[i], a[i])
  • 边界条件:a[i]>sum+a[i]
    arr[] = {6,-1,3,-4,-6,9,2,-2,5}
    sun =
public static int MaxContinueArraySum(int a[]) {
		int sum = a[0];
		for(int i=1;i<a.length;i++){
		    sum = Math.max(sum+a[i], a[i]);
		}
		return sum;
	}

4.数字塔从上到下所有路径中和最大的路径(简单)

          3
        1    5
     8    4    3
  2    6    7    9
6    2    3    5    1
  • 阶段划分:按照层数划分为0i,从左到右路径划分为0j

  • 状态和状态变量:dp[i][j]记录到达第i层第j个数字的最大路径和

  • 状态转移方程:(1) dp[i][j] = dp[i-1][j] + n[i][j],(2) dp[i][j] = Math.max(dp[i-1][j-1]

  • 边界条件:i==0?(1):(2),第0列额外考虑,不然容易发生数组越界
    优化上,可以考虑一维数组代替,因为记录在用过之后就不再使用

      i  
    j 3
      4  8
      12 12  11
      14 18  19  20
      24 21  22  25  21
    
public static int minNumberInRotateArray(int n[][]) {
		int max = 0;
		int dp[][] = new int[n.length][n.length];
		dp[0][0] = n[0][0];
		for(int i=1;i<n.length;i++){
			for(int j=0;j<=i;j++){
				if(j==0){
					//如果是第一列,直接跟他上面数字相加
					dp[i][j] = dp[i-1][j] + n[i][j];
				}else{
					//如果不是第一列,比较他上面跟上面左面数字谁大,谁大就跟谁相加,放到这个位置
					dp[i][j] = Math.max(dp[i-1][j-1], dp[i-1][j]) + n[i][j];
				}
				max = Math.max(dp[i][j], max);
			}
		}
		return max;
	}

5.两个字符串最大公共子序列

str1:BDCABA;str2:ABCBDAB,则这两个字符串的最长公共子序列长度为4,最长公共子序列是:BCBA

  • 阶段划分:按照字符串str1长度划分为0i,按照str2划分为0j

  • 状态和状态变量:dp[i][j]代表A字符串前i行和B字符串第j行最大公共子序列长度

  • 状态转移方程:(1) dp[i][j] = Math.max(dp[i][j-1],dp[i-1][j]), (2) d[i][j]=d[i][j-1]+1;

  • 边界条件:str1[i]==str2[j]?(2):(1);

       B  D  C  A  B  A
    A  0  0  0  1  1  1
    B  1  1  1  1  2  2
    C  1  1  2  2  2  2
    B  1  1  2  2  3  3
    D  1  2  2  2  3  3
    A  1  2  2  3  3  4
    B  1  2  2  3  4  4
    
public class MaxTwoArraySameOrder {
	public static int MaxTwoArraySameOrderMethod(String str1,String str2) {
		int m = str1.length ( );
		int n = str2.length ( );
		int dp[][] = new int[m+1][n+1];
		
		for(int i=0 ; i<=m; i++){
			dp[i][0] = 0;
		}
		for(int i=0 ; i<=n ; i++){
			dp [0][i] = 0;
		}
		
		for(int i=1; i<=m ;i++){
			for(int j=1; j<=n ; j++){
				if( str1.charAt(i-1) == str2.charAt(j-1)){
					dp[i][j] = dp[i-1][j-1]+1;
				}
				else{
				dp[i][j] = Math.max(dp[i][j-1],dp[i-1][j]);
				}
			}
		}
		for(int i=1; i<=m ;i++){
			for(int j=1; j<=n ; j++)
				System.out.print(dp[i][j]+"	");
			System.out.println("row "+i);
		}
		return dp[m] [n];
	}
			
	public static void main (String[] args) {
		String str1 = "BDCABA";
		String str2 = "ABCBDAB" ;
		int array=MaxTwoArraySameOrderMethod ( str1,str2);	
		System.out.println ("max LCS="+array ) ;
	}
}

6.背包问题(中等)

在N件物品取出若干件放在容量为W的背包里,每件物品的体积为W1,W2……Wn(Wi为整数),与之相对应的价值为P1,P2……Pn(Pi为整数),求背包能够容纳的最大价值。

  • 阶段划分:按照物品序列划分为0i,容量大小划分为0j

  • 状态和状态变量:dp[i][j]代表容纳的最大值

  • 状态转移方程:(1)dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w[i]]+p[i]),(2)dp[i][j] = dp[i-1][j]

  • 边界条件:j>=w[i]?(1):(2),第i件能放入和不能放入
    例N=5,W={1,2,5),P={2,3,10)

     	 i
     j   2  2  2
         2  3  3
         2  5  5
         2  5  5
         2  5  10
    
public static int PackageHelper(int n,int w[],int p[],int v) {
		//设置一个二维数组,横坐标代表从第一个物品开始放到第几个物品,纵坐标代表背包还有多少容量,dp代表最大价值
		int dp[][] = new int[n+1][v+1];
		for(int i=1;i<n+1;i++){
			for(int j=1;j<=v;j++){
				if(j>=w[i]){
					/*
					 * 当能放得下这个物品时,放下这个物品,价值增加,但是空间减小,最大价值是dp[i-1][j-w[i]]+p[i]
					 * 当不放这个物品时,空间大,物品还是到i-1,最大价值是dp[i-1][j]
					 * 比较这两个大小,取最大的,就是dp[i][j]
					 */
					dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w[i]]+p[i]);
				}else{
					//如果放不下,就是放上一个物品时的dp
					dp[i][j] = dp[i-1][j];
				}
			}
		}
		return dp[n][v];
	}
	
	public static int PackageHelper2(int n,int w[],int p[],int v) {
			//设置一个二维数组,横坐标代表从第一个物品开始放到第几个物品,纵坐标代表背包还有多少容量,dp代表最大价值
			int dp[] = new int[v+1];
			for(int i=1;i<=n;i++){
				for(int j=v;j>0;j--){
					if(j>w[i]){
						dp[j] = Math.max(dp[j], dp[j-w[i]]+p[i]);
					}else{
						dp[j] = dp[j];
					}
				}
			}
			return dp[v];
		}

7.找零钱问题(中等)

num[i]种面值,找回target数值的零钱有多少种方法
这里是种类问题,如果是找零个数最少,也是同样的,修改dp代表的东西就可以

  • 阶段划分:按照可以使用的硬币种类个数划分为0i,按照找零总数划分为0j
  • 状态和状态变量:dp[i][j]代表找零方法个数
  • 状态转移方程:(1) dp[i][j] = dp][i-1][j] + dp[i][j-num[i]],(2) dp[i][j] = dp[i-1][j]
  • 边界条件:j>num[i]?(1):(2)
public static int SmallMoney(int num[],int target) {
		int m = num.length;
		int dp[][] = new int[m][target+1];
		dp[0][0] = 1;
		for(int i=1;i<=target;i++){
			if(i%num[0] == 0){
				dp[0][i] = 1;//第一行数值填写
			}else{
				dp[0][i] = 0;
			}
		}
		for(int i=0;i<m;i++){
			dp[i][0] = 1;//第一列数值填写
		}
		for(int i=1;i<m;i++){
			for(int j=1;j<=target;j++){
				if(j<num[i]){
					dp[i][j] = dp[i-1][j];
				}else{
					dp[i][j] = dp[i-1][j] + dp[i][j-num[i]];
				}
			}
		}
		return dp[m-1][target];
	}
	//优化方法
public static int SmallMoney2(int num[],int target) {//使用一维数组滚动
		int m = num.length;
		int dp[] = new int[target+1];
		dp[0] = 1;
		for(int i=1;i<=target;i++){
			if(i%num[0] == 0){
				dp[i] = 1;
			}else{
				dp[i] = 0;
			}
		}
		for(int i=1;i<m;i++){
			for(int j=1;j<=target;j++){
				if(j>=num[i]){
					dp[j] = dp[j] + dp[j-num[i]];
				}
			}
		}
		return dp[target];
	}

总结:动态规划主要的思想就是用空间换取时间。把会重复进行计算的小问题的答案存储下来,然后一步步得出大问题的答案,把问题按照阶段划分就是把问题分成小问题,状态转移方程就是小问题答案组成大问题答案,边界值就是根据条件选择不同的状态转移方程。虽然思路是这样,但是如何进行划分,如何考虑条件也是评估我们水平的一个因素。不同的思路得到的方法也是不一样的,归根结底还是思路更重要,动态规划只是一种方法,代表一种思路,想要熟练,还得先多见识一下,总结规律。
参考链接:
https://blog.csdn.net/zw6161080123/article/details/80639932
https://www.jianshu.com/p/6e3dcc476c6a

posted @ 2021-09-22 22:36  zero-ng  阅读(75)  评论(0)    收藏  举报