1.什么是动态规划?
动态规划(Dynamic Programming,DP)是一种用来解决一类最优化问题的算法思想。简单来说,动态规划将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。
而且,比递归好的一点是,动态规划会将每个求解过的子问题记录下来,这样在当下一次碰到同样的问题时,就可以直接使用之前记录的结果,而不是像递归那样重复计算。
2.两个序列问题
(一)最大连续子序列和
最大连续子序列和问题如下:
给定一个数字序列A1,A2,A3…An,求i,j(1<=i<=j<=n), 使得Ai+…+Aj 最大,输出这个最大和。
这题如果暴力来做,复杂度是O(n3),当数据量过大,无法承受。思路是:考虑第i个数,dp[i]代表第i个数为末尾的最长序列和,属于总问题中的一个子问题。
假设dp[i-1]是第i-1个元素为末尾的最长序列和,那么当考虑是否加入第i个元素时,就要做一个判断:当我加入A[i]后,子序列和有没有A[i]本身的值大?
如果加入A[i]后子序列和比A[i]本身的值大,则加入了第i个元素的序列是当前最长子序列;如果加入A[i]后子序列和还没有A[i]本身的值大,那么以A[i]元素结尾的最大子序列中只有A[i]一个元素。
状态方程为: dp[i] = max(a[i],dp[i-1]+a[i])
这样从前往后遍历求dp[i],就能分别求出以每个元素结尾的最大子序列和,再将dp数组排序,最大值即为整个数组的最大子序列和,即各个子问题的最优解构成了原问题的最优解。
代码如下,
package DP;
//最大子序列和(dp)
import java.util.*;
public class Max_Sub_Sum {
public static int max(int a,int b){
if(a>=b){
return a;
}else{
return b;
}
}
public static void main(String[] args) {
int[] a = {-2,11,-4,13,-5,-2};
int []dp = new int[a.length];
dp[0] = a[0];
for(int i = 1 ; i < a.length; i++){
dp[i] = max(a[i],dp[i-1]+a[i]);
}
Arrays.sort(dp);
System.out.println(dp[a.length-1]);
}
}
(2)最长不下降子序列(LIS)
最长不下降子序列问题描述:
在一个数字序列中,找到一个最长的子序列(可以不连续),使得这个子序列是不下降(非递减)的,输出这个最长子序列的元素个数。
这道题暴力来解,复杂度将是灾难性的指数爆炸。
解题思路:
dp[i]表示:以A[i]元素结尾的最长不下降子序列的元素个数。
这里我们把每个位置上的元素都当成一个子序列,外层for循环用来赋初始值,即dp[i] = 1
内层的循环范围是j从0到i-1,即遍历A[i]前面的所有子序列dp[j],我们要判断两个条件:
①A[i]对于dp[j]的最后一个元素A[j]是否非递减
②当子序列dp[j]加入A[i]后,其长度 dp[j] + 1 是否 大于 当前以A[i]元素为结尾的最长子序列个数dp[i]。
当上述两个条件都为“是”的时候,我们令 dp[i] = dp[j] + 1 ,意思是尽量选择一个更长的子序列。
如果子序列dp[j]加入A[i]后,其长度 dp[j] + 1 小于 当前以A[i]元素为结尾的最长子序列个数dp[i],说明还是当前的dp[i]更长,此时保持dp[i]不变,再与下一个dp[j+1]相比较。
最后利用一个ans变量来取得每次遍历一次i的最大值,最终的即为整体问题的最优解,即最长不下降子序列的元素个数。
代码如下:
package DP;
//在一个数字序列中,找到一个最长的子序列(可以不连续),使得这个子序列是不下降的(非递减的)。
import java.util.*;
public class LIS {
public static int max(int a,int b){
if(a>=b){
return a;
}else{
return b;
}
}
public static void main(String[] args) {
int a[] = {1,2,3,-1,-2,7,9};
int []dp = new int[a.length];
dp[0] = 1;
int ans = -1;
for(int i = 0 ; i < a.length; i++){
//设置每个数自己都是一个子序列
dp[i] = 1;
for(int j = 0; j < i; j++){
if(dp[i] < dp[j]+1 && a[j] <= a[i]){
//如果非递减且加入dp[j]这个序列比dp[i]序列本身的长度要大
//则dp[i]序列可以加入dp[j]这个序列。
dp[i] = dp[j] + 1;
}
}
//外层for循环每次循环会得到i位置为结尾的元素的最长子序列。
//所以元素结尾的最长子序列取最大值则为整个数组的最长不下降子序列。
ans = max(ans,dp[i]);
}
System.out.println(ans);
}
}