动态规划思路入门

package day80_alg.base;

import java.util.Scanner;

import org.junit.Test;

/**
 * 求一个数列中连续子数列的值最大!
 * 最大子序列和问题:给定整数A1, A2, ..., An(可能有负数),求其中一段最大的。
 */
public class 动态规划入门 {
	
		
		/**
		 * 方法1:最容易想到的方式:速度O(n*n)
		 * 思路:从1-n遍历所有子序列进行比较,假设遍历到k,那么k包含的子序列在j=i(外层当前遍历值) -> j<arr.len取
		 * ....
		 * ...
		 * ..
		 * .
		 * 	...
		 * 	..
		 * 	.
		 * 	 ..
		 * 	 .
		 * 	  .
		 * 特别注意: j = 0 , 用j <i+1 条件就不行 --> 正向相加要反着连续-1(j=i).可以和上面打...思考!
		 */
		public static int sum0(int[] arr){
			int max = 0; 
			int min = 0; //记录负数最大串
			for (int i = 0; i < arr.length; i++) {
				int sub = 0;
				for (int j = i; j <arr.length; j++) {
					sub += arr[j];
					if(sub > max){
						max = sub;
					}
					if(max==0 && sub<0){ //只有max一直为0,此判断才有用!
						if(min == 0){ //第一个负数进来要给min进行赋值
							min = sub;
						}else if(sub>min){ //判断后面持续进来的负数(子串合)大于前面记录的
							min = sub;
						}
					}
				}
			}
			return max==0?min:max;
		}
		
		/**测试*/
		@Test
		public void test1(){
			 int[] arr1 = new int[]{-4,3,5,2,1,-2,6,-2,-3,-1,1,11,-2};  
			 int[] arr2 = new int[]{-4,-3,-5,-2,-1,-2,-6,-2};  
		     System.out.println("(0)有正有负和为:"+sum0(arr1));  
		     System.out.println("(0)全负数和为:"+sum0(arr2));  
		     System.out.println("(1)有正有负和为:"+sum2(arr1));  
		     System.out.println("(1)全负数和为:"+sum2(arr2)); 
		}
		

	    /**
	     * 
	     * 动态规划:速度 O(n)  --> 未考虑全部负数的。
	     * 步骤1:问题转换(状态的定义):即是:Fk是第k项前的最大序列和,求F1~FN中最大值 -->实现sum0()方法基于此思路!
	     * 步骤2: 问题转换了,那么从上面的定义中找到另外的规律(状态转移方程的定义).
	     * 		别人发现的规律是: Fk=max{Fk-1,0}+Ak // Fk是前k项的和,Ak是第k项的值 .
	     * 		why : 因为后面增加的串的最大子串,不能包含前面一个负数串。在为负数之前,前面的最大串的值已经被记录了。
	     * 		比如,前面有个特别大的值9999,而后面来了2个-5000,-5000,首先9999前最大子串不为负数,为方便计算,假设
	     * 		为0,那么连续-5000就是-1,这个时候,虽然9999很大,只要它想和第二个-5000后面的数组合成大数,那么它就要
	     * 		跨越这2个-5000,而它本身已经被记录,即后面的串重新计算最大子串,但是不需要包含第二个-5000前了。
	     * */
	    public static int sum2(int[] arr){  
	        int max = 0;
	        for (int i = 1; i < arr.length; i++){  
	        	if(arr[i-1] > 0){
	        		arr[i] += arr[i-1];
	        	}
	        	if(arr[i] > max) {
	        		max = arr[i] ;
	        	}
	        }  
	        return max;  
	    } 
		  
	
	    
	    /**
	     * 例子一: 数塔取数问题
		一个高度为N的由正整数组成的三角形,从上走到下,求经过的数字和的最大值。 
		每次只能走到下一层相邻的数上,例如从第3层的6向下走,只能走到第4层的2或9上。
		
		该三角形第n层有n个数字,例如:
		第一层有一个数字:5
		第二层有两个数字:8 4
		第三层有三个数字:3 6 9
		第四层有四个数字:7 2 9 5
		最优方案是:5 + 8 + 6 + 9 = 28
		
		状态定义: Fi,j是第i行j列项最大取数和,求第n行Fn,m(0 < m < n)中最大值。
		状态转移方程:Fi,j = max{Fi-1,j-1,Fi-1,j}+Ai,j
	     */
	    @Test
	    public void test3(){
            Scanner in = new Scanner(System.in);
            int n = in.nextInt();
            long max = 0;
            int[][] dp = new int[n][n];
            dp[0][0] = in.nextInt();
            for (int i = 1; i < n; i++) {
                for (int j = 0; j <= i; j++) {
                    int num = in.nextInt();
                    if (j == 0)
                        dp[i][j] = dp[i - 1][j] + num;
                    else
                        dp[i][j] = Math.max(dp[i - 1][j - 1], dp[i - 1][j]) + num;
                    max = Math.max(dp[i][j], max);
                }
            }
            System.out.println(max);
	    }

	    
	    /**
	     * 例子二:矩阵取数问题,从左角落起,只能往下或往右。直至取完,求最大值。
	     * 1,2,3,3
	     * 5,3,4,2
	     * 3,4,5,6
	     * 1,4,3,2
	     * 公式: Fi,j = max{Fi-1,j-1,Fi,j-1}+Ai,j  
	     * 程序执行过程:从顶角开始,一直取下一个数值,取完换一行,把每一个值最优的路径和取出来放到dp数组中待用
	     * 打个比方: 当算到第三行的第三个5时,它前面可能最优是算4,4中最大+本次取的5。而4位置的cp值就是根据它左边的3
	     * 或上门的3的cp值计算,而上面的3的cp值是2或者(0),就算2最优的cp值,就是1即其实取的dp[1][1]。
	     * 
	     */
	    @Test
	    public void test4(){
	    	int arr[][] = {{1,2,3,3},{5,3,4,2},{3,4,5,6},{1,4,3,2}}; //用直接量表示测试数据方便测试。
	    	int n = 4; //四行四列
	    	int dp[][] = new int[4+1][4+1]; //+1防止 下面的-1越界
	    	dp[1][1] = arr[0][0];  
			for(int i = 1; i < n+1; i++){
				for (int j = 1; j < n+1; j++) {
					dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + arr[i-1][j-1];//arr用的角标比dp少一位
				}
			}
			System.out.println(dp[n][n]);
	    }
	    
	    
 
}

猜你喜欢

转载自blog.csdn.net/shuixiou1/article/details/79539442
今日推荐