最长单调递增子序列(时间复杂度O(nlogn))

写在前面:仅为个人代码/总结,未必标准,仅供参考!如有错误,还望指出交流,共同进步!

最长单调递增子序列

【题目描述】
找出由n个数组成的序列中的最长单调递增子序列及其长度。

【O(n*n)算法解题思路】
思路一:用数组 b[0:n-1]记录以a[i] (0≤i<n) 为结尾元素的最长递增子序列的长度。序列a的最长递增子序列的长度为max {b[i]} (0≤i<n) 。易知,b[i]满足最优子结构性质,可以递归定义为:b[0]=1, b[i]=max{b[k]}+1(0≤k≤i)。
思路二:将原问题转化为求最长公共子序列问题,求原序列和升序排序后序列的最长公共子序列。

【O(nlogn)算法解题思路】
用归纳解此问题。归纳假设是:已知计算序列a[0:i-1] (i<n)的最长递增子序列的长度的正确算法。从初始情况开始,对于长度为n的序列a[0:n-1],设法转换为求长度小于n的序列的最长递增子序列的长度。
定义P:(0≤i<n) k是序列a[0:i]的最长递增子序列的长度; b[K]是序列a[0:i]中所有长度为k的递增子序列中的最小结尾元素值。若w等于某一个b[k]的值,则pre[w]用于存储排在w前面的所有长度为k-1的递增子序列中的最小结尾元素值。
具体解题步骤如下:
在由i-1到i的循环中,
①当a[i]>b[k]时,k=k+1, b[k]=a[i],否则k值不变。
②当a[i]≤b[k]时,如果a[i]<b[1].则将b[1]的值改变为a[i]。当b[1]≤a[i]≤b[k]时,用二分搜索算法找到有序的数组b下标j,使得b[i-1]≤a[i]<b[j]。b[j-1]<a[i]<b[j]时, b[1:j-1]和b[j+1:k]的值不变, b[j]的值改变为a[i]。b[j-1]==a[i]时,保持不变。
③对于记录最长递增子序列,可在求得每一个b[j]时用pre[b[i]]记录b[j]的前驱数据为b[j-1],即排在b[j]前面的序列a[0:i-1]的所有长度为j-1的递增子序列中的最小结尾元素值。

【解题过程遇到的问题】
问:为什么步骤的第②点中当b[j-1]==a[i]时要保持不变?
答:来个例子,比如序列1 2 3 2,当i递增到3时,发现a[i]==b[2],但如果将b[3]改为a[i],这会得到2是长度为3的递增子序列中的最小结尾元素值,这虽然不影响求得原序列的最长单调递增子序列长度为3,但如果要根据数组b来记录最长单调递增子序列则行不通,因为将b[3]改为2后已不满足b[k]的定义,2并不是a[0…3]中长度为3的单调递增子序列的最小结尾值,2,2不构成递增关系。

【样例解答】
在这里插入图片描述
在这里插入图片描述
【实现代码】

#include <bits/stdc++.h>
using namespace std;
int a[101]={0};
int b[101]={0};
int pre[101]={0};
int n;
stack <int> S;
//k是序列a[0:i]的最长递增子序列的长度
//b[k]是序列a[0:i]中所有长度为k的递增子序列中的最小结尾元素值
//假若w等于某一个b[k],则pre[w]存排在w前面的所有长度为k-1的递增子序列中的最小结尾元素值
int binary(int i,int k)
{
    int u=k;
    if(a[i]<b[1])
    {
        return 1;
    }
    //二分法查找满足b[j-1]<=a[i]<b[j]的j
    for(int h=1,j=k;h!=j-1;)
    {
        if(b[k=(h+j)/2]<=a[i])
            h=k;
        else
            j=k;
        u=j;
    }
    return u;
}
int LIS()
{
    int v;
    b[1]=a[0];
    for(int i=1,k=1;i<n;i++)
    {
        if(a[i]>b[k])//a[i]>b[k]
        {
            b[++k]=a[i];
            pre[a[i]]=b[k-1];
        }
        else//a[i]≤b[k]
        {
            int m=binary(i,k);
            if(a[i]!=b[m-1])
            {
                pre[a[i]]=b[m-1];
                b[m]=a[i];
            }
        }
        v=k;
    }
    return v;
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }
    int res=LIS();
    cout<<res<<endl;
    int index=b[res];
    S.push(index);
    while(pre[index]!=0)
    {
        S.push(pre[index]);
        index=pre[index];
    }
    while(!S.empty())
    {
        cout<<S.top()<<' ';
        S.pop();
    }
    return 0;
}
/*
15
7 2 5 6 3 4 9 8 12 13 26 14 30 20 21
*/
/*
7
2 4 3 5 6 4 8
*/
/*
8
12 13 14 1 2 3 2 4
*/
  • 5
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LG.田猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值