LIS模型

之前了解过动态规划中背包模型,今天总结一下动态规划中常用的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]

猜你喜欢

转载自blog.csdn.net/demodan/article/details/80656184
lis