动态规划学习

Posted zero-ng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划学习相关的知识,希望对你有一定的参考价值。

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.数组最大不连续递增子序列

  • 阶段划分:按照数组的元素个数递增划分为0~n
  • 状态和状态变量:使用一维数组temp,temp[n]代表第n个元素为止最大不连续递增子序列个数
  • 状态转移方程:temp[i] = temp[j]+1
  • 边界条件:temp[i]为它前面的比它小的数中对应的temp最大值+1,所以边界为a[i]>a[j]
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]&&temp[j]+1>temp[i]){
					//如果有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.数组最大连续子序列和

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

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

  • 阶段划分:按照层数划分
  • 状态和状态变量:dp[i][j]代表到达第i层第j个数字的最大路径和
  • 状态转移方程:dp[i][j] = Math.max(dp[i-1][j-1], dp[i-1][j]) + n[i][j];
  • 边界条件:第0列只有一条路,所以要分开考虑,不然会发生越界,可以考虑空出第0行,就不用if了
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.两个字符串最大公共子序列

  • 阶段划分:按照字符串的序列划分
  • 状态和状态变量:dp[i][j]代表A字符串前i行和B字符串第j行最大公共子序列长度
  • 状态转移方程:dp[i][j] = Math.max(dp[i][j-1],dp[i-1][j]);
  • 边界条件:如果a[i]=b[j],dp[i][j] = dp[i-1][j-1]+1,否则就比较字符串分别减少一个后的最大公共子序列的值
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.背包问题

  • 阶段划分:按照物品序列划分
  • 状态和状态变量:dp[i][j]代表最大值,i代表从物品只考虑物品1~i,j代表背包容量
  • 状态转移方程:dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w[i]]+p[i]);
  • 边界条件:j>=w[i]背包容量是否大于物品体积
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.找零钱问题

这里是种类问题,如果是找零个数最少,也是同样的,修改dp代表的东西就可以

  • 阶段划分:按照使用的硬币种类个数分
  • 状态和状态变量:dp[i][j]代表方法个数,i代表0~i种硬币,j代表需要找的零钱总数
  • 状态转移方程:dp[i][j] = dp[i-1][j] + dp[i][j-num[i]];
  • 边界条件:j>num[i]
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

以上是关于动态规划学习的主要内容,如果未能解决你的问题,请参考以下文章

代码随想录动态规划算法PDF

动态规划优化篇

强化学习总结 03-动态规划

《数据结构与算法之美》27——初识动态规划

小白学习动态规划:0-1背包

算法学习——动态规划1