最长上升子序列(数据加强版)(动态规划&二分贪心)

文章目录

    • 一.原题
      • 题目描述
      • 输入格式
      • 输出格式
      • 样例输入
      • 样例输出
      • 数据范围
    • 二.做法
      • 1.动态规划
      • 2.二分贪心
        • lower_bound函数
        • 贪心思路
    • 三.总结

一.原题

题目描述

给定一个序列,从中选取若干个数,使得这一组数组成的序列a满足 i < j 且 a[i] < a[j],求这个序列的最长长度

输入格式

第一行一个整数n

接下来一行n个整数表示序列

输出格式

一行一个整数表示最长序列的长度

样例输入

5
1 5 1 2 4

样例输出

3

数据范围

对于30%的数据, n <= 2000

对于100%的数据,n <= 100000

二.做法

1.动态规划

我们可以用数组来存储为开头的最长上升子序列长度,时间复杂度O(n^2),代码如下

#include
using namespace std;
const int M=1e5+5
int x[M],a,y[M],z[M],c=-1005,e,f;
int main(){
	ios::sync_with_stdio(false);
	cin>>a;
	for(int i=1;i<=a;i++) cin>>z[i];
	if(a==1){
		cout<<"1"<<endl<<z[1];
		return 0;
	}
	y[a]=1;
	for(int i=a-1;i>=1;i--){ //反向遍历
		int b=0,d=0;
		for(int j=a;j>i;j--)
			if(z[j]>z[i]&&y[j]>=b){
				b=y[j];
				x[i]=j;
			}
		y[i]=b+1;
		if(y[i]>=e){
			e=y[i];
			f=i;
		}
	}
	cout<<e<<endl;
	while(x[f]!=0){
		cout<<z[f]<<" ";
		f=x[f];
	}
	cout<<z[f]<<endl;
    return 0;
}

但是这种办法虽然面对正常的数据能通过,但明显面对此加强过的数据无法胜任。所以我们有了另一种方法

2.二分贪心

面对这道题,我们可以用贪心的思路来记录最长上升子序列,不过在此我们要先介绍一个C++STL库里的函数

lower_bound函数

lower_bound的语法是(数组名+开始遍历的下标,数组名+结束遍历的下标,一个数a),其功能是用二分的方式找到数组中第一个大于等于a的数。

贪心思路

通过一个数组记录目前已找到的最长上升子序列,实现贪心,因为用了二分的原因,复杂度为O(nlogn),具体代码如下

#include
using namespace std;
const int M=1e5+5,INF=0x3f3f3f3f;
int n,z[M],g[M],mx;
int main(){
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=0;i<n;i++) cin>>z[i];
	memset(g,INF,sizeof(g)); //因为g数组记录已找到最长上升子序列,所以先将g数组极大化
	for(int i=0;i<n;i++){
		int t=lower_bound(g,g+n,z[i])-g; //第一个大于等于z[i]的数
		mx=max(mx,t+1); //最长上升子序列的长度
		g[t]=z[i];  //用最小值更新
	}
	cout<<mx<<endl;
    return 0;
}

这时,时间复杂度的降低,使得我们能通过此题。

三.总结

我们用两种方法分别写出了这道题的代码,但动态规划因为时间复杂度的缘故没能通过,而贪心的思路却能很好的帮助我们通过此题,所以,我想告诉大家的是,在程序设计没有思路时,不妨多想想贪心,可能会发现新大陆哦。

至此,本文完。

你可能感兴趣的:(算法,c++,动态规划,贪心算法)