最长上升子序列-LIS

动态规范: 

分析过程来自 http://www.cnblogs.com/GodA/p/5180560.html 特此借鉴。

我们都知道,动态规划的一个特点就是当前解可以由上一个阶段的解推出, 由此,把我们要求的问题简化成一个更小的子问题。子问题具有相同的求解方式,只不过是规模小了而已。最长上升子序列就符合这一特性。我们要求n个数的最长上升子序列,可以求前n-1个数的最长上升子序列,再跟第n个数进行判断。求前n-1个数的最长上升子序列,可以通过求前n-2个数的最长上升子序列……直到求前1个数的最长上升子序列,此时LIS当然为1。

  让我们举个例子:求 2 7 1 5 6 4 3 8 9 的最长上升子序列。我们定义d(i) (i∈[1,n])来表示前i个数以A[i]结尾的最长上升子序列长度。

  前1个数 d(1)=1 子序列为2;

  前2个数 7前面有2小于7 d(2)=d(1)+1=2 子序列为2 7

  前3个数 在1前面没有比1更小的,1自身组成长度为1的子序列 d(3)=1 子序列为1

  前4个数 5前面有2小于5 d(4)=d(1)+1=2 子序列为2 5

  前5个数 6前面有2 5小于6 d(5)=d(4)+1=3 子序列为2 5 6

  前6个数 4前面有2小于4 d(6)=d(1)+1=2 子序列为2 4

  前7个数 3前面有2小于3 d(3)=d(1)+1=2 子序列为2 3

  前8个数 8前面有2 5 6小于8 d(8)=d(5)+1=4 子序列为2 5 6 8

  前9个数 9前面有2 5 6 8小于9 d(9)=d(8)+1=5 子序列为2 5 6 8 9

所以  状态转移方程 d[ i ] = max{d[ i ] ,d[ j ]+1}  此处F[ j ] 表示 

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n;
    int a[1000];
    int d[1000];
    while(cin>>n,n)
    {
        int maxlen=-1e5;
        for(int i=1;i<=n;i++)
            cin>>a[i];
    //由此,把我们要求的问题简化成一个更小的子问题。
    //子问题具有相同的求解方式,只
    //不过是规模小了而已。
    //最长上升子序列就符合这一特性。
    //我们要求n个数的最长上升子序列,
    //可以求前n-1个数的最长上升子序列,
    //再跟第n个数进行判断。
    //求前n-1个数的最长上升子序列
        for(int i=1;i<=n;i++)
        {
            d[i]=1;
            for(int j=1;j<i;j++)  //扫描 第n个数 前面的
            {
                if(a[i]>a[j])  //找到一个比它小的  进行状态转移
                {
                    d[i] = max(d[i],d[j]+1);
                }
            }
           maxlen=max(maxlen,d[i]);
        }
        cout<<maxlen<<endl;
    }
    return 0;
}
//时间复杂度O(n^2) 

这个算法的时间复杂度为〇(n²),并不是最优的算法。在限制条件苛刻的情况下,这种方法行不通。那么怎么办呢!有没有时间复杂度更小的算法呢?说到这里了,当然是有的啦!还有一种时间复杂度为〇(nlogn)的算法,下面就来看看。

二分+贪心:

我们再举一个例子:有以下序列A[]=3 1 2 6 4 5 10 7,求LIS长度。

  我们定义一个B[i]来储存可能的排序序列,len为LIS长度。我们依次把A[i]有序地放进B[i]里。(为了方便,i的范围就从1~n表示第i个数)

  A[1]=3,把3放进B[1],此时B[1]=3,此时len=1,最小末尾是3

  A[2]=1,因为1比3小,所以可以把B[1]中的3替换为1,此时B[1]=1,此时len=1,最小末尾是1

  A[3]=2,2大于1,就把2放进B[2]=2,此时B[]={1,2},len=2

  同理,A[4]=6,把6放进B[3]=6,B[]={1,2,6},len=3

  A[5]=4,4在2和6之间,比6小,可以把B[3]替换为4,B[]={1,2,4},len=3

  A[6]=5,B[4]=5,B[]={1,2,4,5},len=4 

  A[7]=10,B[5]=10,B[]={1,2,4,5,10},len=5

  A[8]=7,7在5和10之间,比10小,可以把B[5]替换为7,B[]={1,2,4,5,7},len=5

  最终我们得出LIS长度为5。但是,但是!!这里的1 2 4 5 7很明显并不是正确的最长上升子序列。是的,B序列并不表示最长上升子序列,它只表示相应最长子序列长度的排好序的最小序列。这有什么用呢?我们最后一步7替换10并没有增加最长子序列的长度,而这一步的意义,在于记录最小序列,代表了一种“最可能性”。假如后面还有两个数据8和9,那么B[6]将更新为8,B[7]将更新为9,len就变为7。读者可以自行体会它的作用。

因为在B中插入的数据是有序的,不需要移动,只需要替换,所以可以用二分查找插入的位置,那么插入n个数的时间复杂度为〇(logn)  这样总时间复杂度是不是就是O(nlogn).

#include <bits/stdc++.h>
using namespace std;
int main()
{
    const int INF=0x7f7f7f7f;
    int n;
    int len=1;
    int a[1000];
    int b[1000];
    while(cin>>n,n)
    {
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            b[i] = INF;
        }
        for(int i=1;i<=n;i++)
        {
            int next=lower_bound(b+1,b+n+1,a[i])-b;  //二分插入位置
            b[next] = a[i];
            len = max(next,len);
        }
        /*for(int j=1;j<=len;j++)
            cout<<b[j]<<' '<<endl;*/
       cout<<len<<endl;
    }
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值