最长上升子序列的两种算法(LIS)

最长上升子序列就是求:给定的一串数字中 找出不断上升的最长那一串(该串不一定相连,只保证递增即可)。就比如下面这个例子   其最长上升子序列为2 3 4 7或2 3 4 6  数串长度为4 那么具体怎么求的呢

我们拿一个最简单的例子讲:

【题目描述】

给定N个数,求这N个数的最长上升子序列的长度

【样例输入】

7

2 5 3 4 1 7 6

【样例输出】

4

一、朴素算法   (复杂度O(n^2))

dp[i]表示从0~i的以a[i]结尾的最大上升子序列长度,而以a[i]结尾的最长上升子序列有两种:1.只包含a[i]的子序列(即为其自身长度dp[i]=1);  2.在满足j<i且a[j]<a[i]的以a[j]为结尾的上升子序列末尾,追加上a[i]得到的子序列(即dp[i]=a[j]+1)。
所以有如下递推关系:

dp[i]的值为max(dp[j]+1,dp[i])也就是:

i 0 1 2 3 4 5 6
a[i] 2 5 3 4 1 7 6
dp[i] 1 2 2 3 1 4 4

没看懂表格没关系,下面是具体解释 对a[i]从头开始遍历,第一个数是2,由于2前面没有数字,则到2的最大长度就是1(其本身)即dp[0]=1。接着a[1]=5。a[1]>a[0].那么这时候dp[2]=dp[1]+1=2; a[2]=3,往前遍历3<5不符合条件,3>2满足dp[3]=dp[1]+1=2;a[3]=4,往前遍历找满足条件的最大值 4>3,dp[3]=dp[2]+1=3,4<5不符合条件,4>2 dp[1]+1=2<(dp[3]=3),则dp[3]=3。以此类推 。。。   

下面上代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
    int n,dp[100010],a[100010];
    int i,j,maxn;
    while(~scanf("%d",&n)){
        for(i=0;i<n;i++){
            scanf("%d",&a[i]);
        }
        memset(dp,0,sizeof(dp));
        for(i=0;i<n;i++){
            dp[i]=1;
            for(j=i;j>=0;j--){//往前遍历,找到以a[i]结尾的最大值
                if(a[i]>a[j]){
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
        }
        maxn=0;
        for(i=0;i<n;i++){//遍历dp[i]找到最大值
            if(maxn<dp[i]){
                maxn=dp[i];
            }
        }
        printf("%d\n",maxn);
    }
    return 0;
}

二、二分法(复杂度nlogn)

用dp[i]表示数字串长度i的时对应的最小末尾值。然后令 i = 1 to 9 逐个考察这个序列。

此外,我们用一个变量Len记录目前最长算到多少了

还是以上面的题举例

首先,把a[1]有序地放到dp里,令dp[1] = 2,表示当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时的Len = 1。

接着,a[2] = 5,a[2] > dp[1],所以令dp[1+1] = dp[2] = a[3] = 5,表示长度为2的LIS的最小末尾是5,这时候dp[1..2] = 2, 5,这时的Len = 2。

接着,a[3] = 3,它正好夹在了1和
5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是把5淘汰掉,这时候dp[1..2] = 2, 3,这时Len = 2。


继续,a[4] = 4,它在3后面,因为dp[2] = 3, 而4在3后面,于是很容易可以推知dp[3] = 4, 这时dp[1..3] = 2, 3, 4,这时的Len = 3。

 a[5] = 1,这时候往前遍历你会发现a[5]<dp[1],则这时 将dp[1]替换为a[5]即dp[1]=a[5]=1,此时dp[1..2]=1,3,4

a[6] = 7,它很大,比4大,于是dp[4] = 7,这时的Len = 4。


 a[7] = 6,dp[3]<a[7]<dp[4]得到dp[4] = 6,这时的Len = 4。

注意。这个1,3,4,6不是LIS,它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。
下面给出例题模版:

用STL写:

#include<stdio.h>  
#include<string.h>  
#include<algorithm>  
using namespace std;  
#define INF 0x3f3f3f  
int dp[10010];//dp[i]表示长度为i+1的子序列末尾元素最小值;   
int a[10010];  
int main()  
{  
    int n;  
    while(scanf("%d",&n)!=EOF)  
    {  
        for(int i=0;i<n;i++)  
        {  
            scanf("%d",&a[i]);  
            dp[i]=INF;//不可以用memset对数组赋值INF,只能赋值0或-1;  
                      //可以用fill(dp,dp+n,INF);   
        }  
        for(int i=0;i<n;i++)  
        {  
            *lower_bound(dp,dp+n,a[i])=a[i];//找到>=a[i]的第一个元素,并用a[i]替换;   
        }  
        printf("%d\n",lower_bound(dp,dp+n,INF)-dp);//找到第一个INF的地址减去首地址就是最大子序列的长度;   
    }  
    return 0;  
} 

猜你喜欢

转载自blog.csdn.net/xiao_you_you/article/details/80426946