LIS最长上升子序列(线性dp,贪心+二分)


一、背景

最长公共子序列
给定一个序列,问其中最长的递增子序列的长度。
子序列,说明可以不连续。
但此题数据过大,一般线性dp将会超时


二、线性DP

1.思路

时间复杂度为 O ( n 2 ) O(n^2) O(n2)
先确定一个状态: d p [ i ] dp[i] dp[i]表示以 a [ i ] a[i] a[i]作为子序列结尾时最长子序列的长度。
那么有 d p [ j ] = m a x { d p [ i ] + 1 } a [ j ] > a [ i ] , 0 ≤ i < j dp[j]=max\{ dp[i]+1\}_{a[j]>a[i],0\leq i<j} dp[j]=max{dp[i]+1}a[j]>a[i],0i<j
这样遍历一遍过后可以得到所有位置的解,最后再遍历一遍就得到了满足题意的最优解。

2.代码

#include<iostream>
#include<algorithm>
using namespace std;
int main(){
	int n;
	cin>>n;
	int a[n],b[n];
	for(int i=0;i<n;i++)cin>>a[i],b[i]=1;
	for(int i=1;i<n;i++){
		for(int j=0;j<i;j++){
			if(a[i]>a[j])b[i]=max(b[i],b[j]+1);
		}
	}
	for(int i=0;i<n;i++)b[0]=max(b[0],b[i]);
	cout<<b[0];
} 

三、贪心+二分

1.思路

时间复杂度为 O ( n   l o g   n ) O(n\ log\ n) O(n log n)

如果在处理完的数据中定义一个数组 l o w [   ] low[\ ] low[ ] l o w [ a n s ] low[ans] low[ans]表示长度为 a n s ans ans的子序列集合中,诸多结尾中最小的值,比如长度为 3 3 3的子序列有 {   { 1 、 2 、 3 } , { 4 、 5 、 6 } , { 7 、 8 、 9 } } \{\ \{1、2、3\},\{4、5、6\},\{7、8、9\}\} { {123},{456},{789}},那么此时 l o w [ 3 ] low[3] low[3]的值为 3 3 3
这里其实是采用了贪心的想法:保证此时长度为 a n s ans ans的结尾元素的值尽可能的小,那么就有利于后续接上,也就可以得出最长的上升子序列。

我们先以 a n s ans ans来表示已经处理过的数据的 L I S LIS LIS,此时下一个问题是如何对这个 l o w low low数组进行维护更新,当此时有一个数据 t m p tmp tmp:
1.当 t m p > l o w [ a n s ] tmp>low[ans] tmp>low[ans],也就是 t m p tmp tmp可以接在此时最长的子序列后面,那么此时的 a n s ans ans就需要进行更新 ⟹ l o w [ + + a n s ] = t m p \Longrightarrow low[++ans]=tmp low[++ans]=tmp
2.当 t m p < l o w [ a n s ] tmp <low[ans] tmp<low[ans],即无法用来直接更新 L I S LIS LIS,那么我们要如何处理这个 t m p tmp tmp呢?

不妨以一个例子来说明,给定一个序列 { 1 , 2 , 6 , 8 , 4 } \{1,2,6,8,4\} {1,2,6,8,4}
处理完前四个数据后,有表如下:

l o w [ 1 ] low[1] low[1] l o w [ 2 ] low[2] low[2] l o w [ 3 ] low[3] low[3] l o w [ 4 ] low[4] low[4]
1268

首先, 4 4 4当然可以作为长度为 1 1 1的子序列的最后一个元素,即自己本身,但此时 l o w [ 1 ] low[1] low[1] 1 1 1,这不满足我们的贪心策略;
再看 l o w [ 2 ] low[2] low[2] 2 2 2, l o w [ 2 ] < 4 low[2]<4 low[2]<4仍然不可用来更新,但此时 l o w [ 3 ] low[3] low[3] 6 6 6 l o w [ 3 ] > 4 low[3]>4 low[3]>4,这表明4完全可以在原来长度为 2 2 2的子序列后面续接,同时得到一个长度为 3 3 3且结尾元素小于原来的 l o w [ 3 ] low[3] low[3]的子序列,那么此时就需要更新 l o w [ 3 ] low[3] low[3]的值了:

l o w [ 1 ] low[1] low[1] l o w [ 2 ] low[2] low[2] l o w [ 3 ] low[3] low[3] l o w [ 4 ] low[4] low[4]
1248

此时我们再观察这个 l o w [ 3 ] low[3] low[3] l o w low low数组中与 4 4 4的关系,可以发现 l o w [ 3 ] low[3] low[3] l o w low low数组中第一个大于 4 4 4的值
但是如果此时继续加入一个值为 4 4 4的数据,又要如何处理呢?
此时要求的是最长上升子序列,如果把此时的 l o w [ 4 ] low[4] low[4]更新为4,所得的是最长非降序子序列,所以为了格式的统一,不妨将上述的更新改为更新第一个大于等于 t m p tmp tmp的值,同时可以使用

lower_bound(beg,end,val)
//返回一个迭代器,指向非递减序列[first, last)中的第一个大于等于(>=)val的位置**

来简化代码。

2.代码

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N];
int low[N];
int n;
int ans;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	memset(low,0x3f,sizeof low);
	ans=1;
	low[ans]=a[1];
	for(int i=2;i<=n;i++){
		if(a[i]>low[ans]){
			low[++ans]=a[i];
		}
		else{
			//int tmp=lower_bound(low+1,low+ans, a[i])-low;//返回第一个大于等于a[i]的位置,将该处的值进行更新
			low[lower_bound(low+1,low+ans, a[i])-low]=a[i]; 
		}
	}
	cout<<ans;
	return 0;
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值