设 a[i] 表示序列中的第 i 个数, f[i] 表示 1 - i 这一段中以 i 结尾的最长上升子序列的长度,初始时设f [i] = 0(i = 1, 2, ..., n)。则有动态规划方程:f[i] = max{1, f[j] + 1} (j = 1, 2, ..., i - 1, 且a[j] < a[t] )。
但如上的直接实现时间复杂度是O(n)的。
设有两个元素 a[x] 和 a[y] ,满足
I. x < y < i II. a[x] < a[y] < a[i] III. f[x] = f[y]
此时,选择f[x]和选择f[y]都可以得到同样的F[i]值,那么,在最长上升子序列的这个位置中,应该选择a[x]还是应该选择a[y]呢?
很明显,选择a[x]比选择a[y]要好。因为由于条件 II ,在a[x+1] ... a[i-1]这一段中,如果存在a[z],a[x] < a[z] < a[y],则与选择a[y]相比,将会得到更长的上升子序列。
再根据条件 III ,可以得出结论:对于F[i] 的每一个取值 len,我们只需要保留满足F[i] = len 的所有a [i] 中的最小值。设 d[len] 记录这个值,即 d[len] = min{ a[i] } (f[i] = len)。
而对于数组 d[] 来讲:d[1] < d[2] < d[3] < ... < d[n]
利用 d[] ,我们可以得到另外一种计算最长上升子序列长度的方法。
设当前已经求出的最长上升子序列长度为len。先判断 a[i] 与 d[len] :
--若 a[i] > d[len],则将 a[i] 接在 d[len] 后将得到一个更长的上升子序列,len += 1, d[len] = a[i];
--否则,在 d[1]..d[len] 中,找到最大的j,满足 d[j] < a[i]。令 len = j + 1,更新 d[len] = a[i]。最后,len 即为所要求的最长上 升子序列的长度。
在 上述算法中,若使用朴素的顺序查找满足条件的 d[],每次计算时的复杂度是O(n),整个算法O(n^2),与原来的算法相比没有任何进步。
但是由于 d[] 的单调性,我们在 d[] 中查找时,可以使用二分查找高效地完成,时间复杂度降至O(nlogn)。
PS:
d[]在算法结束后记录的并不是一个符合题意的最长不下降子序列。
参考代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
int a[100],d[100],f[100];
int ans,n;
int find(int len,int n)//若返回值为x,则 d[x] >= n > d[x-1]
{
int l = 0, r = len, mid = ( l+r )/2;
while(l<=r)
{
if( n > d[mid] ) l = mid+1;
else if ( n < d[mid] ) r = mid-1;
else return mid;
mid = ( l+r )/2;
}
return l;
}
int main(void)
{
while(cin>>n)
{
memset(d,1,sizeof(d));
for(int i=0;i<n;i++)
cin>>a[i];
d[0]=-1;
d[1]=a[0];
f[0]=1;
for(int i=1;i<n;i++)
{
int j=find(n+1,a[i]);
d[j]=a[i];
f[i]=j;
}
ans=0;
for(int i=0 ; i<n ; i++)
if( f[i] > ans )
ans = f[i];
cout<<ans<<endl;
}
return 0;
}
本文介绍了一种求解最长上升子序列问题的有效算法。通过维护一个候选序列d[]并结合二分查找技术,该算法能在O(nlogn)的时间复杂度内找到最长上升子序列的长度。
&spm=1001.2101.3001.5002&articleId=14454321&d=1&t=3&u=6f305172c15648d29579524cc2a449b1)
1227

被折叠的 条评论
为什么被折叠?



