题目:
设计一个O(n²)时间的算法,找出由n个数组成的序列的最长单调递增子序列。
思路分析:
O(n²)算法的复杂度很大一部分浪费在查找前i-1项的最长上升子序列上。如果我们能直接保存前i项的最大上升子序列,那么当我们处理第i项时只需要调用即可。
如果同样长的子序列,当然它的尾部元素越小越好。所以设s[i]为长度为i的子序列尾部元素的最小值。我们可以知道s[i]序列是单调递增的,因为如果s[i]>s[i+1],那么取s[i+1]中序列的前i个元素代替s[i],所得的s’[i]<s[i],这和s[i]的定义矛盾。所以这里我们可以知道s[i]序列是单调递增的。
对于第i个数a[i],我们为了维护s[]数组,找到其对应的恰好大于a[i]的一个数,用a[i]去代替它。为什么这样就可以维护s[]数组?因为当如果a[i]>s[k-1]且a[i]<=s[k],那么a[i]至少可以接到长度为k-1的序列之后,得到长度为k的序列,又因为原来长度为k的序列的最小值比a[i]小,所以用a[i]去替代它。
最后输出s[]序列能达到的最大长度即可。
因为s[i]序列是单调递增的,那么我们可以用二分的方法去查找,复杂度为O(logn)。再考虑遍历n个元素,所以总的时间复杂度为O(nlogn)。
代码:
#include<bits/stdc++.h>
#define N 1100
using namespace std;
int len,n,a[N],s[N];
int _find(int t)
{
int l=1,r=len;
while(l<r)
{
int mid=(l+r)>>1;
if(s[mid]>=t) r=mid;
else l=mid+1;
}
return l;
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
s[len=1]=a[1];
for(int i=2;i<=n;i++)
if(a[i]>s[len])
s[++len]=a[i];
else
{
int pos=_find(a[i]);
s[pos]=a[i];
}
cout<<"maxlen="<<len<<endl;
}
}