动态规划
拆分问题,定义问题状态和状态之间的关系,每一状态解决一次,使得问题能够以递推的方式去解决。
动态规划其实感觉和递归想法差不多,就是递归每次每一步都要重复计算,导致复杂度很高,动态规划
就是每一步就计算一次,把这一状态保存下来,下回用的时候直接就用了,很高效。
动态规划解题的一般思路
1. 将原问题分解为子问题
把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(数字三角形例)。
子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。
2.确定状态
在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。
所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 在数字三角形的例子里,一共有N×(N+1)/2个数字,所以这个问题的状态空间里一共就有N×(N+1)/2个状态。
整个问题的时间复杂度是状态数目乘以计算每个状态所需时间。在数字三角形里每个“状态”只需要经过一次,且在每个状态上作计算所花的时间都是和N无关的常数。
3.确定一些初始状态(边界状态)的值
以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值。
4. 确定状态转移方程
定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”
主要就是确定初始状态和这个状态转移方程,一般好像都是重后往前推的,初始状态确定也是确定最后的边界,然后重后往前找一个有规律的状态转移方程。
01背包
package com.lq.exercise04;
import java.util.Scanner;
public class Exercise0407 {
public static void main(String[] args){
Scanner scan=new Scanner(System.in);
int n=scan.nextInt();
int m=scan.nextInt();
System.out.println(n+" "+m);
int[] w=new int[n+1];
int[] v=new int[m+1];
for(int i=1;i<=n;i++){
w[i]=scan.nextInt();
v[i]=scan.nextInt();
}
int[][] dp=new int[n+1][m+1];
for(int i=0;i<=m;i++){
dp[0][i]=0;
}
for(int i=0;i<=n;i++){
dp[i][0]=0;
}
for(int i=1;i<=n;i ++){
for(int j=1;j<=m;j++){
if(w[i]<=j){
dp[i][j]=Math.max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
}
else{
dp[i][j]=dp[i-1][j];
}
}
}
System.out.println(dp[n][m]);
}
}
public class Exercise0404 {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
int n = s.nextInt();
if(n <= 1 || n > 1000)
return;
int[] array = new int[n + 1];
for(int i = 0;i <= n;i++)
array[i] = s.nextInt();
printResult(array);
}
public static void printResult(int[] array) {
int length = array.length;
long[][] dp = new long[length][length];
long sum;
for(int len = 2;len < length;len++) {
for(int i = 1, j = len;j < length;i++, j++) {
long min = Long.MAX_VALUE;
for(int k = i;k < j;k++) {
sum = dp[i][k] + dp[k + 1][j] + array[i - 1] * array[k] * array[j];
if(min > sum)
min = sum;
}
dp[i][j] = min;
}
}
System.out.println(dp[1][length - 1]);
return;
}
}
最大连续子序列和:
给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ...,
Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个,
例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和
为20。
在今年的数据结构考卷中,要求编写程序得到最大和,现在增加一个要求,即还需要输出该
子序列的第一个和最后一个元素。
//sum每次都加,max记录下当前最大的,如果sum是负数,则置为0重新往后开始加,因为sum为负数时再往后加也一定比重新加小,没有可能是最大的,也是动态记录子序列和的状态。
public class Exercise0503 {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
while(scan.hasNext()){
int n=scan.nextInt();
if(n==0){
break;
}
int[] a=new int[n];
for(int i=0;i<n;i++){
a[i]=scan.nextInt();
}
int start,end,sum,temp,count,max;
start=end=sum=temp=count=0;
max=a[0];
for(int i=0;i<n;i++){
sum+=a[i];
if(sum>max){
System.out.println(temp);
end=i;
max=sum;
start=temp;
System.out.println(start+" "+temp+" "+end);
}else if(sum<0){ //如果是负数肯定不是最大的,再加一个肯定也没有加的那个大,所以重新开始计数。
temp=i+1;
sum=0;
count++;
}
}
if(count==n){
System.out.println("0"+" "+a[0]+" "+a[n-1]);
}else{
System.out.println(max+" "+a[start]+" "+a[end]);
}
}
}
}