O(n^2)時遞推關係簡單, 代碼實現也簡潔, 唯一的問題是n ^ 2的複雜度在題目給的數據量較大時會超時。
這個問題可以用二分來優化。
做法是構造出一個新的有序的DP數列, 用原數列中的數從左到右維護更新新數列。
初始時DP[0] = s[0], 從i = 1時遍歷原數列, 將每個遍歷的數與DP數列的末尾進行比較, 如果大於末尾, 則把DP數列長度提1將s[i]放在DP數列的最後, 如果小於末尾那麼替換掉DP數列中比s[i]大的第一個數。
結束後DP數列的長度就是LIS的長度。
從LIS的性質出發,要想得到一個更長的上升序列,該序列前面的數必須儘量的小。
對於序列1,5,8,3,6,7來說,當子序列爲1,5,8時,遇到3時,序列已經不能繼續變長了。但是,我們可以通過替換,使“整個序列”看上去更小,從而有更大的機會去變長。這樣,當替換5-3和替換8-6完成後(此時序列爲1,3,6),我們可以在序列末尾添加一個7了。
那爲什麼複雜度可以是O(NlogN)呢?
關鍵就在“替換”這一步上,若直接遍歷序列替換,每次替換都要O(N)的時間。但是隻要我們再次利用LIS的性質——序列是有序的(單調的),就可以用二分查找,在O(logN)的時間內完成一次替換,所以算法的複雜度是O(NlogN)的。
代碼:
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int n;
int a[1100];
int dp[1100];//記錄最大子序列
int pos[1100];//記錄最大子串的下標
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
int j=0;
dp[0]=a[0];
pos[0]=0;
for(int i=1;i<n;i++){
if(a[i]>dp[j]){
j++;
dp[j]=a[i];
pos[j]=i;
}
else{
//flag表示dp中>=a[i]的第一個數的下標
int flag=lower_bound(dp,dp+j,a[i])-dp;
// cout<<flag<<endl;
// cout<<*lower_bound(dp,dp+j,a[i])<<endl;
dp[flag]=a[i];
pos[flag]=flag;
}
}
cout<<j+1<<endl;
// 最長子序列的和
// int res=0;
// for(int i=0;i<=j;i++){
// res+=dp[pos[i]];
// }
// cout<<res<<endl;
return 0;
}