【模板代码】最长上升子序列模板

1. 题目

2. 算法:

1)思路:

        采用二分的方式,找出序列 f[] 中,最接近且小于 即将插入数字a[i] 的位置f[r],将a[i]插入或代替在f[r+1]中

2)步骤:

①初始化+定义数组:

· a[] 输入序列

· f[i] 以i为结尾的最长上升子序列长度 

· n 输入序列长度

· len (输出)最长子序列的长度

· 初始化 f[ ]  为最大值 0x3f

· l 当前最长上升子序列位置下标最左端 初始值为0

  r 当前最长上升子序列位置下标最右端   

  mid 当前最长上升子序列位置下标中间 

②二分

从前往后找 f[]中最接近且小于a[i]的数

(朴素版本:动规 一看就懂)

for(int i=1;i<=n;i++)
{
	f[i]=1;
	for(int j=1;j<=i-1;j++)
	{
		if(a[j]<a[i])
		{
			f[i]=max(f[i],f[j]+1);//比较原来和加上这个数的序列的长短,选更长的	
		}
	}
}

但是因为两重循环,时间复杂度O(n²) 会超,且 f[] 存最长上升子序列,为单调递增序列,所以我们采用更高效的二分来解决

(二分)核心代码:

for(int i=1;i<=n;i++)
{
	int l=0,r=len,mid;
	while(l<r)
	{
		mid=(l+r+1)/2;
		if(a[i]>f[mid]) l=mid;
		else r=mid-1;
	}
	len=max(len,r+1);
	f[r+1]=a[i];
}

大概过程如下(如图:题目样例)

1. 我们希望前面的数字在同样的情况下,尽可能地小,这样后面的数字就尽可能地多。

    e.g. 1 5 3 4 

            若 f[1]=1  f[2]=5  则后面没有数字可以接上,len1=2;

            若f[1]=1  f[2]=3   则后面还可以接4  len2=3 >len1

            以此类推

2. 如果当前a[i] 小于f[mid] (从小到大),则说明a[i] 存在于f[mid] 左侧,所以将右边界移到mid-1,在f[]左侧找a[i];如果当前a[i] 大于f[mid], 则说明a[i]存在于f[mid]右侧,所以将左边界移到mid处,在f[]右侧找a[i]。

if(a[i]>f[mid]) l=mid;
else r=mid-1;

3. 为什么mid=(l+r+1)/2而不是(l+r)/2?

会死循环!

若不加1 结果如下:

e.g.   1 4 5 7 9

l=1时 r=2 mid=(1+2)/2=1

此时 f[mid]=1,a[2]=4>f[mid]

∴l=mid=1 

我们发现会一直如此循环,因为新的l与原来的l相同,mid,r 均不发生改变,导致了死循环

③输出:len
cout<<len;

 3. 完整代码:

二分:(满分)

#include<bits/stdc++.h>
using namespace std;
int a[100005],f[100005];
int len=0; 
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=n;i++)
	{
		int l=0,r=len,mid;
		while(l<r)
		{
			mid=(l+r+1)/2;
			if(a[i]>f[mid]) l=mid;
			else r=mid-1;
		}
		len=max(len,r+1);
		f[r+1]=a[i];
	}
	cout<<len;
	return 0;
}

朴素动规版:(40分)

#include<bits/stdc++.h>
using namespace std;
int f[5010];
int a[5005];
int main()
{
	f[1]=1;
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	} 
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i-1;j++)
		{
			if(a[j]<a[i])
			{
				f[i]=max(f[i],f[j]+1);	
			}
		}
	}
	int maxn=-0x3f;
	for(int i=1;i<=n;i++)
	{	
		if(f[i]>maxn) maxn=f[i];
	}
	cout<<maxn;
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值