最長遞增子序列---nlogn的解法
阿新 • • 發佈:2018-12-15
題目描述:
給出長度為N的陣列,找出這個陣列的最長遞增子序列。(遞增子序列是指,子序列的元素是遞增的)
例如:5 1 6 8 2 4 5 10,最長遞增子序列是1 2 4 5 10。
Input
第1行:1個數N,N為序列的長度(2 <= N <= 50000) 第2 - N + 1行:每行1個數,對應序列的元素(-10^9 <= Sii <= 10^9)
Output
輸出最長遞增子序列的長度。
Sample Input
8
5
1
6
8
2
4
5
10
Sample Output
5
思路:
我們如果用一般的01揹包做法是兩重for迴圈複雜度為O(n2),若題目中數字不到1e4及以上就會超時,所以我們需要優化程式碼降低複雜度,因為在兩層for迴圈中的第二層是用來尋找數值比當前小且長度最長的dp[j],所以我們可以用二分查詢的方法來減少迴圈次數。
以上題為例,
len=0;
i=1時,dp[len++]=5;(即dp[1]=5)
i=2時,dp[1]=1;(此時,遍歷dp,用二分法尋找最大的len值並且dp[len]小於a[i])
i=3時,dp[2]=6;
i=4時,dp[3]=8;
i=5時,dp[2]=2; //不斷地用更小的值更新dp,這樣才能使得以某長度結尾的數最小,
i=6時,dp[3]=4; // 才能在以後接上更多的數字。
i=7時,dp[4]=5;
i=8時,dp[5]=10;
所以,可以求得最大的長度為5;
程式碼:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int MAXN=50005; int a[MAXN],dp[MAXN],len; int binary_search(int i){ int left,right,mid; left=1,right=len; while(left<right){ mid = left+(right-left)/2; //二分查詢,尋找使得dp[mid]>=a[i]的最大mid值需要用>=號 if(dp[mid]>=a[i]) right=mid; else left=mid+1; } return left; } int main() { int n; scanf("%d",&n); for(int i=1; i<=n; ++i) scanf("%d",&a[i]); dp[1] = a[1]; len=1; for(int i=2; i<=n; ++i){ if(a[i]>dp[len]) dp[++len]=a[i]; // 如果用STL,pos=lower_bound(ans,ans+len,arr[i])-ans; else{ int pos=binary_search(i); dp[pos] = a[i]; } } printf("%d\n",len); return 0; }