AcWing 896. 最长上升子序列 II(贪心 + 二分 lower_bound)

通过贪心思想结合二分查找实现最长上升子序列问题的高效求解,介绍了三种实现方式,包括利用STL的二分库函数和手动编写二分函数的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
本题是AcWing 895. 最长上升子序列的数据强化版,数据范围 n <= 1e5,可知原来 O(n ^ 2) 的做法已经行不通了。因此我们至少应当将时间复杂度控制在 O(nlogn) 以内。

思路:

贪心 + 二分(二分查找思想回顾

贪心思想:对于 最大上升子序列结尾元素越小,越 有利于后面接上其他的数,也就 变得更长

贪心策略:建立一个 q 数组,元素 q[i] 表示 长度为 i 的 LIS 结尾元素最小值,因此我们只需要维护 q 数组即可。

具体措施

设 原数组为 aq 数组中元素 q[i] 表示 长度为 i 的 LIS 结尾元素最小值q 数组长度为 len

首先数组 a 中存入 n 元素,数组 q 用来存结果,最终 数组 q 的长度 len 就是最终的答案。

假如数组 q 现在存了数,当到了数组 a 的第 i 个位置时,首先判断 a[i]>q[len] ,若大于,则直接将这个数添加到数组 q 末尾,即 q[++len]=a[i]

a[i]<=q[len] 时,我们就用 a[i] 去替代数组 q 中的 大于等于 a[i] 的第一个数,因为在整个过程中 ,数组 q 是一个递增的数组,所以我们可以用 二分查找O(logn) 的时间复杂度下 找到目标位置,然后覆盖,即:q[l] = a[i]

a[i] 覆盖 q[l] 含义:a[i] 为最后一个数 的 LIS,其中元素 个数为 l

遍历完整个数组 a 后 我们就可以得到 最终的结果len

时间复杂度:

O ( n l o g n ) O(nlogn) O(nlogn)

代码:

这里给出三种做法,前两种是本文介绍的做法,第三种有点不一样,但是更好写,推荐比赛时候使用

① 利用 STL 的二分库函数 lower_bound

#include<bits/stdc++.h>

using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i) scanf("%d", &a[i]);
    
    int len = 0;
    q[++len] = a[1];
    
    for(int i=2; i<=n; ++i)
    {
        if(a[i]>q[len]) q[++len] = a[i];
        
        else q[lower_bound(q+1, q+len+1, a[i])-q] = a[i];
    }

    cout<<len<<endl;
    
    return 0;
}

② 手打二分函数

#include<bits/stdc++.h>

using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i) scanf("%d", &a[i]);
    
    int len = 0;
    q[++len] = a[1];
    
    for(int i=2; i<=n; ++i)
    {
        if(a[i]>q[len]) q[++len] = a[i];
        else
        {
            int l = 1, r = len;
            while(l<r)
            {
                int mid = l+r>>1;
                if(q[mid]>=a[i]) r = mid;
                else l = mid+1;
            }
            q[r] = a[i];
        }
    }

    cout<<len<<endl;
    
    return 0;
}

③ 目前为止感觉很简便的写法 减少了判断 很好理解 思想与上文中略有不同

  • 下标从 1 开始
//lower_bound:
#include<bits/stdc++.h>

using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i) scanf("%d", &a[i]);
    
    int len = 0;
    
    for(int i=1; i<=n; ++i)
    {
        int t = lower_bound(q+1, q+len+1, a[i]) - q;
        q[t] = a[i];
        if(t>len) ++len; //查找到了 q 数组末尾元素的下一个位置,说明没找到目标,则将 a[i] 添加至末尾元素的下一个位置,长度 len+1
    }

    cout<<len<<endl;
    
    return 0;
}
//手打二分:
#include<bits/stdc++.h>

using namespace std;

const int N = 1e5+10;
int n;
int a[N], q[N];

int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i) scanf("%d", &a[i]);
    
    int len = 0;
    for(int i=1; i<=n; ++i)
    {
        int l = 1, r = len+1;
        while(l<r)
        {
            int mid = l+r>>1;
            if(q[mid]>=a[i]) r = mid;
            else l = mid+1;
        }
        q[l] = a[i];
        if(l>len) ++len;
    }
    
    cout<<len<<endl;
    
    return 0;
}
  • 下标从 0 开始
//lower_bound:
#include<bits/stdc++.h>

using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];

int main()
{
    cin>>n;
    for(int i=0; i<n; ++i) scanf("%d", &a[i]);
    
    int len = 0;
    
    for(int i=0; i<n; ++i)
    {
        int t = lower_bound(q+1, q+len+1, a[i]) - q;
        q[t] = a[i];
        if(t>len) ++len; //查找到了 q 数组末尾元素的下一个位置,说明没找到目标,则将 a[i] 添加至末尾元素的下一个位置,长度 len+1
    }

    cout<<len<<endl;
    
    return 0;
}
//手打二分
#include<bits/stdc++.h>

using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];

int main()
{
    cin>>n;
    for(int i=0; i<n; ++i) scanf("%d", &a[i]);
    
    int len = 0;
    
    for(int i=0; i<n; ++i)
    {
        int l = 1, r = len+1;
        while(l<r)
        {
            int mid = l+r>>1;
            if(q[mid]>=a[i]) r = mid;
            else l = mid+1;
        }
        q[l] = a[i];
        if(l>len) ++len;
    }

    cout<<len<<endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值