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
今日推荐
周排行