之前了解过动态规划中背包模型,今天总结一下动态规划中常用的LIS模型(最长递增子序列),许多动态规划算法题是由其变形可解。最长递增子序列,有时间复杂度为o(n^2)解法,有优化后的O(nlogn)解法
1.最长递增子序列
问题描述:给定序列,求出序列中最长递增子序列
分析:要求长度为i的序列{a1,a2,…..ai}的最长递增子序列,需要先求出长度为i-1的序列中以各元素结尾的最长递增子序列。然后将这些最长递增子序列与元素ai进行比较,如果某个长度为m的子序列,其末尾元素aj比ai小,那么将ai加入该序列中,将得到一个新的以ai结尾的长度为m+1的子序列。否则,长度不变。将处理后的所有子序列的长度进行比较,取其中长度最长的子序列,作为以ai结尾的子序列。
list数组:存放以ai结尾的最长递增子序列长度
pre数组:存放以ai结尾的序列的上一个数
状态转移方程:list[i] = max{list[j] + 1} ,其中j>=0,j < i
举例:
import java.util.Arrays;
import java.util.Stack;
/**
* 最长递增子序列
*/
public class LIS {
public static void main(String[] args){
int[] a = {1,2,5,4,10,7,6,8};
printLIS(a);
}
private static void printLIS(int[] a) {
if (a == null || a.length == 0)
return;
int length = a.length;
int[] list = new int[length];//存放以a[i]结尾的最长递增长度
int[] pre = new int[length];//存放以a[i]结尾的最长递增长度的前驱
int max = 0;
int index = -1;
for (int i = 0; i < length; i++){
list[i] = 1;
pre[i] = -1;
for (int j = 0; j < i; j++){
if (a[j] < a[i] && list[i] < list[j]+1) {
list[i] = list[j] + 1;
pre[i] = j;
}
}
max = max > list[i] ? max : list[i];
index = max > list[i] ? index : i;
}
System.out.println(max);
Stack<Integer> s = new Stack<Integer>();
while (index != -1) {
s.push(a[index]);
index = pre[index];
}
while (!s.isEmpty())
System.out.print(s.pop()+" ");
}
}
总结:时间复杂度O(N^2),可进一步优化
2.导弹拦截
问题描述:某国为防御敌国的导弹袭击,发展出一种导拦截系统,这种导弹拦截系统有一个缺陷,虽然他的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前已发的高度,某天,雷达捕捉到敌国来袭,由于该系统还在试用阶段,所以只有一套系统,因为有可能不能拦截所有的导弹,输入导弹一次飞来的高度,计算这套系统最多能拦截多少导弹?
分析:此问题是LIS的变形题,求最长递增子序列的长度即可。
进阶:如果拦截所有导弹最少需装配多少套这样的导弹系统?(Dilworth原理)
3.合唱队形
问题描述:n位同学站成一排,音乐老师要请其中的N-K位同学出列,使得剩下的k位同学排成合唱队形。合唱队形是指一种队列:设k位同学从左到右的编号依次为1,2,…..k,他们的身高分别为T1,T2,…Tk,则他们的身高满足T1< T2<…Ti,Ti>Ti+1>….Tk,(i<=k,i>=1),你的任务是,已知有n位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱形。
分析:合唱队形要求先上升后下降的最长子序列
状态设计:
rise[i]:以i结尾的最长递增子序列
decline[i]:以i开始的最长递减子序列
结果为length - max{rise[i]+decline[i]-1}
/**
* 合唱队形
*/
public class ChorousFormation {
public static void main(String[] args){
int[] height = {186,186,150,200,160,130,197,220};
selectPerson(height);
}
/**
* 选人出列
* @param height
*/
private static void selectPerson(int[] height) {
if (height == null || height.length == 0)
return;
int length = height.length;
int[] rise = new int[length];
int[] decline = new int[length];
for (int i = 0; i < length; i++){//以i结尾的最长上升子序列
rise[i] = 1;
for (int j = 0; j < i; j++){
if (height[j] < height[i] && rise[i] < rise[j] + 1)
rise[i] = rise[j] + 1;
}
}
for (int i = length-1; i >= 0; i--){//以i开始的最长下降子序列
decline[i] = 1;
for (int j = length-1; j > i; j--){
if (height[i] > height[j] && decline[i] < decline[j] + 1)
decline[i] = decline[j] + 1;
}
}
int max = 0;
for (int i = 0; i < length; i++){
int temp = rise[i] + decline[i] - 1;
max = max > temp ? max : temp;
}
System.out.println(length-max);
}
}
4.上帝选人
问题描述:世界上的人都有智商和情商,我们用俩个数字分别表示人的智商和情商,数字大就代表相应的属性高,现在在你面前有n个人,这n个人的智商和情商均已知,请你选择尽量多的人,满足选择的人中任意俩人i和j(i > j),i的智商大于j的智商并且i的情商大于j的情商。
分析:此题仍然是LIS变形题,只不过在传统的LIS上多了一个判断条件
状态转移方程:
dp[i] = max{dp[j]+1},其中 j>=0 j < i,并且iq[i] > iq[j],eq[i] > eq[j]