最长上升子序列(LIS)

最长上升子序列

最长上升子序列(Longest Increasing Subsequence,简称LIS)是指给定一个序列,找出其中最长的严格递增的子序列。这个子序列不一定是连续的,但其中的元素在原序列中的相对顺序保持不变。例如,对于序列 [3, 4, -1, 0, 6, 2, 3],其最长上升子序列可以是 [3, 4, 6],长度为3。

朴素做法 O(n^2):

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int dp[N];
int a[N];
int n;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    int res=-2e9;
    for(int i=1;i<=n;i++){
        for(int j=i;j>=1;j--){
            if(a[j]<a[i]) dp[i]=max(dp[j]+1,dp[i]);
        }
        res=max(res,dp[i]);
    }
    cout<<res;
    return 0;
}

相当暴力,时间复杂度O(n^2)。每一个dp[i]状态都可以由所有它左侧值比它小的转移过来。相当于暴力找左侧dp[j](j<=i)的最大值。但是这样加一个pre[N]记录一下每个节点的前驱的下标,其实可以回溯出整个序列的值。

贪心+二分优化 O(nlogn):
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main(void) {
    int n; cin >> n;
    vector<int>arr(n);
    for (int i = 0; i < n; ++i)cin >> arr[i];

    vector<int>stk;//模拟堆栈
    stk.push_back(arr[0]);

    for (int i = 1; i < n; ++i) {
        if (arr[i] > stk.back())//如果该元素大于栈顶元素,将该元素入栈
            stk.push_back(arr[i]);
        else//替换掉第一个大于或者等于这个数字的那个数
            *lower_bound(stk.begin(), stk.end(), arr[i]) = arr[i];
    }
    //for(int i=0;i<stk.size();i++) cout<<stk[i]<<' ';输出后不难发现顺序并不都是理想的上升序列,但长度是一样的
    cout<<stk.size()<<endl;
    return 0;
}
  • 贪心:
    • 如果即将入栈的数据arr[i]比栈顶stk.back()的大,就直接插入。
    • 如果入栈的数据arr[i]比栈顶stk.back()的小,由于栈是单调递增的,就通过二分找到第一个大于等于arr[i]的数,将其替换。这里是更新stk这个栈的增长潜力。比如: 1 7 3 5 61 71 3哪一个的增长潜力大?显然是1 3,因为1 3后能接上的数据范围为4到正无穷,而1 7只能接上8到正无穷。从概率角度来讲,如果后面的数据未知的话,显然是1 3的增长的概率更大。这就是贪心的地方了。

而在一堆单调的数字中,找到某个具体数字的位置,最快的显然就是二分了。

这个比较简单,我本来是不打算特意写一篇博客的。但是直到下午睡傻了,1 3 5 9 7 4 8 10的最长上升子序列按二分加贪心得到的是1 3 4 7 8 10,顺序明显不对啊。。然后过了好一会儿才反应,这本来就是用来快速求序列长度的,为了保持栈的增长潜力,在进行一些更新的时候会破坏序列顺序,这种破坏有益的一面就是增加了栈的增长潜力,但不利的一面就是,并不是每一次这种破坏都是有效的,只有刚好更新栈内最后一个数据才行。但是前面无意义的更新(提升增长潜力)其实是为了能更新到栈内最后一个数据埋下伏笔。

比如:序列:1 6 7 3 4 5 2,栈内:1 6 7
第一次3想入栈,小于7,更新为:1 3 7
第二次4想入栈,小于7,更新为:1 3 4
第三次5想入栈,小于7,更新为:1 3 4 5
第四次2想入栈,小于7,更新为:1 2 4 5
此时栈内的最长上升子序列的顺序变了,但是类似于等价替换,改变了顺序,并不会影响序列的长度,只是增加了这串序列的增长潜力。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值