最长递增子序列(LIS)求解

问题描述

最长递增子序列也称 “最长上升子序列”,简称LIS ( longest increasing subsequence)。设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lis=<ak1,ak2,…,akm>,其中k1<k2<…<km且ak1<ak2<…<akm。求最大的m值。

如:求一个一维数组arr[i]中的最长递增子序列的长度,如在序列{ 7, 1, 6, 5, 3, 4, 8 }中,最长递增子序列长度为4,其递增子序列为:1,3,4,8。

问题解决

对于这类问题比较直观的想法是用动态规划,就像最长公共子序列(LCS)求解方法一样,对于动态规划问题,往往存在递推解决方法,需要首先建立递推关系。

方法1,DP求解

定义dp[i] : dp[i]为以arr[i]结尾的最长递增子序列的长度。对于第i个元素其递增序列长度最小值为1 即其自身。对于以arr[i]结尾的递增序列,分两组情况:

A,只包含arr[i]的子序列; B, 对于满足 j<i 且arr[i] > arr[j] 的以arr[j] 为结尾的递增子序列,然后追加上arr[i]得到的子序列;

对应的递推关系为:

dp[i] = max{1,dp[j]+1 | j<i 且 arr[j]<arr[i]}

使用这一递推公式即可完成LIS求解问题,代码如下:

#include <cstdio>
#include <algorithm>

const int MAX=16;

int dp[MAX];
//DP
//dp[i]表示以arr[i]为末尾的最长上升子序列的长度
int solve_lis_1(int *arr, int n)
{
    if(!arr || n<1)
        return -1;
    int res=0;
    for(int i=0; i<n; i++)
    {
        dp[i]=1;
        for(int j=0; j<i; j++)
        {
            if(arr[j]<arr[i])
                dp[i]=std::max(dp[i], dp[j]+1);
        }
         res=std::max(res, dp[i]);
    }
    return res;
}
int main()
{
    int a[]={7,1,5,3,4};
    printf("LIS:%d\n", solve_lis_1(a, sizeof(a)/sizeof(int)));
    return 0;
}

其时间复杂度显然为 O(n^2).

方法2,DP+二分搜索

我们期望在前i个元素中的所有长度为len的递增子序列中找到这样一个序列,它的最大元素比arr[i+1]小,而且长度要尽量的长,如此,我们只需记录len长度的递增子序列中最大元素的最小值就能使得将来的递增子序列尽量地长。

维护一个数组dp[i],记录所有长度为i+1的递增子序列中末尾元素的最小值,并对于数组中的每个元素考察其是那个子序列的末尾元素,二分更新dp数组,最终i的值便是最长递增子序列的长度。总的说来就是,dp[i] 表示长度为i+1的上升子序列中末尾元素的最小值.

最开始全部dp[i]的值初始化为无穷大 INF,然后由前向后逐个考虑序列的元素,对于某个aj ,如果i=0或者dp[i-1] < aj, 就用dp[i]=min(dp[i], aj)进行更新。最终找出

dp[i]<INF的最大i+1就是答案了。

代码如下:

/*
 Problem:DP LIS,动态规划求解最长递增子序列
 Mem:挑战程序设计 P64
*/
#include <cstdio>
#include <algorithm>

const int MAX=16;

int dp[MAX];
//DP
//dp[i]表示以arr[i]为末尾的最长上升子序列的长度
int solve_lis_1(int *arr, int n)
{
    if(!arr || n<1)
        return -1;
    int res=0;
    for(int i=0; i<n; i++)
    {
        dp[i]=1;
        for(int j=0; j<i; j++)
        {
            if(arr[j]<arr[i])
                dp[i]=std::max(dp[i], dp[j]+1);
        }
         res=std::max(res, dp[i]);
    }
    return res;
}

// DP + 二分搜索
//dp[i] 表示长度为i+1的上升子序列中末尾元素的最小值
int solve_lis_2(int *arr, int n)
{
    const int INF=0xffffff;
    int i;
    for(i=0; i<n; i++)// 初始化dp[i]为 INF
        dp[i]= INF;
    for(i=0; i<n; i++)
    {
        *std::lower_bound(dp, dp+n, arr[i]) = arr[i];
    }
    return std::lower_bound(dp, dp+n, INF)-dp;
}

int main()
{
    int a[]={7,1,5,3,4};
    printf("LIS:%d\n", solve_lis_1(a, sizeof(a)/sizeof(int)));
    printf("LIS:%d\n", solve_lis_2(a, sizeof(a)/sizeof(int)));
    return 0;
}

其中二分查找算法是利用STL的lower_bound 函数 这个函数在有序序列中利用二分搜索技术找到指向满足 ai>=k的ai最小指针,参见这里

对于序列{7,1,5,3,4} 方法 solve_lis_2 dp数组变化如图所示。

方法2的时间度为:因为二分查找的复杂度为O(logn) 加上外层循环,总体复杂度为 O(nlogn)。

参考:

http://www.ahathinking.com/archives/117.html

最长递增子序列(LIS)求解

时间: 2024-10-14 11:21:16

最长递增子序列(LIS)求解的相关文章

动态规划(DP),最长递增子序列(LIS)

题目链接:http://poj.org/problem?id=2533 解题报告: 状态转移方程: dp[i]表示以a[i]为结尾的LIS长度 状态转移方程: dp[0]=1; dp[i]=max(dp[k])+1,(k<i),(a[k]<a[i]) #include <stdio.h> #define MAX 1005 int a[MAX];///存数据 int dp[MAX];///dp[i]表示以a[i]为结尾的最长递增子序列(LIS)的长度 int main() { int

最长递增子序列 LIS 时间复杂度O(nlogn)的Java实现

关于最长递增子序列时间复杂度O(n^2)的实现方法在博客http://blog.csdn.net/iniegang/article/details/47379873(最长递增子序列 Java实现)中已经做了实现,但是这种方法时间复杂度太高,查阅相关资料后我发现有人提出的算法可以将时间复杂度降低为O(nlogn),这种算法的核心思想就是替换(二分法替换),以下为我对这中算法的理解: 假设随机生成的一个具有10个元素的数组arrayIn[1-10]如[2, 3, 3, 4, 7, 3, 1, 6,

算法--字符串:最长递增子序列LIS

转自:labuladong公众号 很多读者反应,就算看了前文 动态规划详解,了解了动态规划的套路,也不会写状态转移方程,没有思路,怎么办?本文就借助「最长递增子序列」来讲一种设计动态规划的通用技巧:数学归纳思想.  最长递增子序列(Longest Increasing Subsequence,简写 LIS)是比较经典的一个问题,比较容易想到的是动态规划解法,时间复杂度 O(N^2),我们借这个问题来由浅入深讲解如何写动态规划. 比较难想到的是利用二分查找,时间复杂度是 O(NlogN),我们通过

算法面试题 之 最长递增子序列 LIS

找出最长递增序列 O(NlogN)(不一定连续!) 参考 http://www.felix021.com/blog/read.php?1587%E5%8F%AF%E6%98%AF%E8%BF%9E%E6%95%B0%E7%BB%84%E9%83%BD%E6%B2%A1%E7%BB%99%E5%87%BA%E6%9D%A5 我就是理解了一下他的分析 用更通俗易懂的话来说说题目是这样 d[1..9] = 2 1 5 3 6 4 8 9 7 要求找到最长的递增子序列首先用一个数组b[] 依次的将d里面

POJ 1836 Alignment 最长递增子序列(LIS)的变形

大致题意:给出一队士兵的身高,一开始不是按身高排序的.要求最少的人出列,使原序列的士兵的身高先递增后递减. 求递增和递减不难想到递增子序列,要求最少的人出列,也就是原队列的人要最多. 1 2 3 4 5 4 3 2 1 这个序列从左至右看前半部分是递增,从右至左看前半部分也是递增.所以我们先把从左只右和从右至左的LIS分别求出来. 如果结果是这样的: A[i]={1.86 1.86 1.30621 2 1.4 1 1.97 2.2} //原队列 a[i]={1 1 1 2 2 1 3 4} b[

poj 2533 Longest Ordered Subsequence 最长递增子序列(LIS)

两种算法 1.  O(n^2) 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 6 int a[1005]; 7 int dp[1005]; 8 int main() 9 { 10 int n, maxn; 11 while(scanf("%d", &n) != EOF) 12 { 13 maxn = 0; 14 for(

最长递增子序列 (LIS) Longest Increasing Subsequence

问题描述: 有一个长为n的数列a0, a1,..., an-1.请求出这个序列中最长的上升子序列.请求出这个序列中最长的上升子序列. 上升子序列:对于任意i<j都满足ai<aj的子序列. 限制条件 i <= n <= 1000 0 <= ai <= 1000000 两种定义方式 具体看程序注释 1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #inc

动态规划 - 最长递增子序列LIS

问题:一个序列有N个数:A[1],A[2],-,A[N],求出最长非降子序列的长度 样例输入:3 1 2 6 5 4 思路: 首先把问题简单化.可以先求A[1],...A[i]的最长非降子序列,令dp[i]为以A[i]结尾的最长非降子序列.当i = 1 时, 明显是长度dp[1] = 1 : i = 2 时,前面没有比1小的数字,故dp[2]=1 , 此时的最长非降子序列为1 ; i = 3 时,比数字2小的数是1 ,并且只有1 , 分析可知 dp[3] = dp[2]+1:当i = 4 时,找

最长递增子序列LIS递归算法

#include<iostream> using namespace std; int minStep,n,*arr,*record,*lis,index,recordMax,lisCount; /* 1.minStep :存放"只"遍历一次指定数组,得到的LIS的长度.比如: *arr={4,5,1,2,3}; 遍历该数组过后,minStep=2,即为{4,5} 两个元素的长度.具体请看getMinStep方法. 2.*arr : 存放输入的,或者随机产生的一组数; 3.

关于【最长递增子序列(LIS)】

拦截导弹 题目描述: 某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度.某天,雷达捕捉到敌国的导弹来袭,并观测到导弹依次飞来的高度,请计算这套系统最多能拦截多少导弹.拦截来袭导弹时,必须按来袭导弹袭击的时间顺序,不允许先拦截后面的导弹,再拦截前面的导弹. 输入: 每组输入有两行, 第一行,输入雷达捕捉到的敌国导弹的数量k(k<=25), 第二行,输入k个正整数,表示k枚导弹的高度,按