nlogn的LIS(最长不**子序列)

本文介绍了一种求解最长上升子序列问题的有效算法。通过维护一个候选序列d[]并结合二分查找技术,该算法能在O(nlogn)的时间复杂度内找到最长上升子序列的长度。

设  a[i]  表示序列中的第 i 个数, f[i] 表示 1 - i 这一段中以 i 结尾的最长上升子序列的长度,初始时设f [i] = 0(i = 1, 2, ..., n)。则有动态规划方程:f[i] = max{1, f[j] + 1} (j = 1, 2, ..., i - 1, 且a[j] < a[t] )。

但如上的直接实现时间复杂度是O(n)的。 

设有两个元素 a[x] 和 a[y] ,满足 
I.   x < y < i                  II.  a[x] < a[y] < a[i]              III.   f[x] = f[y] 

此时,选择f[x]和选择f[y]都可以得到同样的F[i]值,那么,在最长上升子序列的这个位置中,应该选择a[x]还是应该选择a[y]呢? 

很明显,选择a[x]比选择a[y]要好。因为由于条件 II ,在a[x+1] ... a[i-1]这一段中,如果存在a[z],a[x] < a[z] < a[y],则与选择a[y]相比,将会得到更长的上升子序列。 
再根据条件 III ,可以得出结论:对于F[i] 的每一个取值 len,我们只需要保留满足F[i] = len 的所有a [i] 中的最小值。设 d[len] 记录这个值,即 d[len] = min{ a[i] } (f[i] = len)。 

而对于数组 d[] 来讲:
d[1] < d[2] < d[3] < ... < d[n]


利用 d[] ,我们可以得到另外一种计算最长上升子序列长度的方法。

设当前已经求出的最长上升子序列长度为len。先判断 a[i] 与 d[len] :

--若 a[i] > d[len],则将 a[i] 接在 d[len] 后将得到一个更长的上升子序列,len += 1, d[len] = a[i];

--否则,在 d[1]..d[len] 中,找到最大的j,满足 d[j] < a[i]。令 len = j + 1,更新 d[len] = a[i]。最后,len 即为所要求的最长上 升子序列的长度。 

在 上述算法中,若使用朴素的顺序查找满足条件的 d[],每次计算时的复杂度是O(n),整个算法O(n^2),与原来的算法相比没有任何进步。

但是由于 d[] 的单调性,我们在 d[] 中查找时,可以使用二分查找高效地完成,时间复杂度降至O(nlogn)。

PS:

d[]在算法结束后记录的并不是一个符合题意的最长不下降子序列。

参考代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
int a[100],d[100],f[100];
int ans,n;
int find(int len,int n)//若返回值为x,则  d[x]  >=  n > d[x-1]
{
    int l = 0, r = len, mid = ( l+r )/2;
    while(l<=r)
    {
        if( n > d[mid] ) l = mid+1;
        else if ( n < d[mid] )  r = mid-1;
                else  return mid;
        mid = ( l+r )/2;
    }
    return l;  
}
     
     
int main(void)
{
    
    while(cin>>n)
    {
        memset(d,1,sizeof(d));
        for(int i=0;i<n;i++)
            cin>>a[i];
        d[0]=-1;    
        d[1]=a[0];       
        f[0]=1;   
        for(int i=1;i<n;i++)
        {
            int j=find(n+1,a[i]);
            d[j]=a[i];
            f[i]=j;
        }  
        ans=0;
        for(int i=0 ; i<n ; i++)
            if( f[i] > ans )
                ans = f[i];
       cout<<ans<<endl;
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值