最长上升子序列模型(2种常用)

最长上升子序列模型
(1)朴素版本(时间复杂度O(n^2))

#include <bits/stdc++.h>
#define MAX 0x3f3f3f3f
using namespace std;
const int maxn = 1e3+10;
int n,a[maxn],dp[maxn];//dp[i]表示长度为i的数组中最长上升子序列长度是多少
int main(){
    scanf("%d",&n);//数组里元素个数
    for(int i=1; i<=n; i++) scanf("%d",&a[i]);//输入数组
    for(int i=1; i<=n; i++){
        dp[i]=1;//每个位置初始化为1,因为开始更新时只有它一个元素
        for(int j=1; j<i; j++){
     	//只要前面有比a[i]小的元素a[j],那就更新j点的长度以及利用j点长度更新i点长度
            if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1);
        }
    }
    //找最长上升子序列的长度
    int res=-MAX;
    for(int i=1; i<=n; i++) res=max(res,dp[i]);
    printf("%d",res);
    return 0;
}

(2)二分+贪心版本(时间复杂度O(nlog(n)))
核心思想:
利用二分进行维护最长上升子序列,但是要注意的是,这个最长上升子序列是不一定正确的。

举例好懂一点:
我们再举一个例子:有以下序列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,但是,但是!!!B[ ] 中的序列并不一定是正确的最长上升子序列。在这个例子中,我们得到的1 2 4 5 7 恰好是正确的最长上升子序列,下面我们再举一个例子:有以下序列A[ ] = 1 4 7 2 5 9 10 3,求LIS长度。

A[1] = 1,把1放进B[1],此时B[1] = 1,B[ ] = {1},len = 1
A[2] = 4,把4放进B[2],此时B[2] = 4,B[ ] = {1,4},len = 2
A[3] = 7,把7放进B[3],此时B[3] = 7,B[ ] = {1,4,7},len = 3
A[4] = 2,因为2比4小,所以把B[2]中的4替换为2,此时B[ ] = {1,2,7},len = 3
A[5] = 5,因为5比7小,所以把B[3]中的7替换为5,此时B[ ] = {1,2,5},len = 3
A[6] = 9,把9放进B[4],此时B[4] = 9,B[ ] = {1,2,5,9},len = 4
A[7] = 10,把10放进B[5],此时B[5] = 10,B[ ] = {1,2,5,9,10},len = 5
A[8] = 3,因为3比5小,所以把B[3]中的5替换为3,此时B[ ] = {1,2,3,9,10},len = 5

最终我们得出LIS长度为5。但是,这里的1 2 3 9 10很明显并不是正确的最长上升子序列。因此,B序列并不一定表示最长上升子序列,它只表示相应最长子序列长度的排好序的最小序列。这有什么用呢?我们最后一步3替换5并没有增加最长子序列的长度,而这一步的意义,在于记录最小序列,代表了一种“最可能性”,只是此种算法为计算LIS而进行的一种替换。假如后面还有两个数据12和15,那么B[ ]将继续更新。

#include <bits/stdc++.h>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
int a[maxn];
int low[maxn];//存的是长度为i结尾的子序列中最大值
int n,cnt;//cnt为最长子序列长度
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin >> n;
    for(int i=1; i<=n; i++) cin >> a[i];
    low[++cnt]=a[1];//初始化第一个位置
    for(int i=2; i<=n; i++){
    	//如果大于最长上升子序列中最大值,那就把他接在后面,同时最长上升子序列长度+1
        if(a[i]>low[cnt]) low[++cnt]=a[i];
        //否则,利用二分法找到最长上升子序列中第一个大于等于a[i]的元素并进行修改。
        else{
            low[lower_bound(low+1,low+1+cnt,a[i])-low]=a[i];
        }
    }
    cout << cnt;
    return 0;
}

还有一种是树状数组的方式,但时间复杂度和二分一样,所以就先不展示了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值