按照上述递归方程,从dp[0]求解到dp[n-1]的非递归实现如下所示:
#include<iostream>
#define MAX 1010
using namespace std;
int len[MAX];
int val[MAX];
int main(){
int n, rs=1;
cin>>n;
for(int i=0; i<n; ++i){
cin>>val[i];
len[i]=1;
for(int j=0; j<i; j++){
if(val[j]<val[i] && len[i]<len[j]+1)
len[i] = len[j]+1;
}
if(rs<len[i]) rs=len[i];
}
cout<<rs<<endl;
return 0;
}
上述实现的时间复杂度为O(n^2),主要是因为在求解dp[i]时,需要遍历所有的dp[0]~dp[i-1]。而这也是优化的关键点,即如何减少每次对前面i项的遍历。试想一下,如果某个val[j]<val[i],使得dp[i]更新为dp[j]+1,那么所有小于dp[j]的项其实无需再考虑。根据这个想法,我们可以根据子序列的长度二分前面的i项。但是还有一个问题,如何处理子序列长度相同的那些项。一种方法是把它们都保存下来,当二分到该长度时,找到第一个末尾数小于val[i]的项或找不到为止。这种方法在最坏的情况下与dp无异,且实现起来徒增麻烦。第二种方法则是对于长度相同的项,只记录下末尾数最小的项,因为如果这一项都无法满足小于val[i],其他项就更不可能满足这个条件。根据第二种方法,我们只需要建立一个len数组,其中len[k]表示当前长度为k的递增子序列最小的末尾数是len[k]。考虑第i项时,只需二分len数组已存在的下标范围,找到满足len[k]<val[i]且k最大的位置,并用val[i]更新len[k+1],如果len[k+1]在更新前没赋值过,就将len数组的最大下标增一,最后的结果就是len数组的最大下标。实现代码如下:
#include<iostream>
#include<vector>
#define MAX 1010
using namespace std;
vector<int> len;
// 这里我返回的满足len[k]>=val[i]且k最小的位置
// 和上文红色部分的描述是等价的,只是变成了更新len[k],而不是len[k+1]
int bisearch(int val){
int left=0, right=len.size()-1;
while(left<=right){
int mid = (left+right)>>1;
if(len[mid] < val) left = mid+1;
else right = mid-1;
}
return left;
}
int main(){
int n, val;
cin>>n;
for(int i=0; i<n; ++i){
cin>>val;
int k = bisearch(val);
if(len.size()<=k) len.push_back(val);
else len[k] = val;
}
cout<<len.size()<<endl;
return 0;
}