最長單增子序列問題
阿新 • • 發佈:2019-01-24
問題描述:有一個長為n的數列a0,a1,a2........a(n-1)。請求出這個序列中最長的單增子序列的長度。單增子序列的定義是:對於任意的 i<j,都滿足ai<aj。
這個問題就是著名的最長單增子序列(LIS)問題。對於這道問題,我們可以利用動態規劃來進行求解:假設dp[i]表示以a[i]為末尾的最長單增子序列的長度,則在得到dp[i]時,我們可以這樣做:初始化dp[i]為1,利用一個j變數遍歷已經訪問過的陣列a中的值,如果此時a[i]>a[j],表示我們可以在原來的子序列之後加上一個構成一個新的以ai結尾的單增子序列,這時,如果dp[i]的值小於dp[j]+1的值時,我們就將其更新。這樣我們就可以得到:時間複雜度為O(n^2)。
dp[i] = max{dp[j] + 1, 1} if j<i and a[j] < a[i]。
接下來我們換一種思路來想,如果子序列的長度相同的話,那麼取得的末尾值越小就越有優勢。基於這種思路我們可以利用這樣的假設:dp[i]表示長度為i+1的上升子序列中末尾元素的最小值。首先我們對dp陣列用無窮大INF進行初始化,對於每一個ai,如果j==0,或者dp[j-1]<a[i],我們就用dp[j] = min(dp[j], a[i])來進行更新。這樣我們仍然需要一個兩層的迴圈,但是可以進行優化,對於內層的迴圈:由於dp陣列是單增的(可以用反證法證明,如果i<j,而dp[i] > dp[j],則在以dp[j]結尾的單增子序列中的第i位一定小於dp[i],這樣就違背了當初對dp陣列的假設),我們可以使用一個二分搜尋來找的我們需要更新的位置,即在dp陣列中找到大於或等於a[i]且最近的位置,這樣就將時間複雜度降低為O(n*logn)。#include<iostream> #define max(a, b) ((a)>(b)?(a):(b)) const int INF = 1000000; const int n = 6; int a[n] = {4, 2, 3, 1, 5, 5}; int dp[n]; /** dp[i]表示以a[i]為末尾的最長單增子序列的長度 */ int dp1(){ int res = 0; for (int i = 0; i < n; i++){ dp[i] = 1; for (int j = 0; j < i; j++) { if (a[i] > a[j]) { dp[i] = max(dp[i], dp[j]+1); } } res = max(dp[i], res); } return res; } int main(){ printf("%d\n", dp1()); system("pause"); return 0; }
#include<iostream> #define max(a, b) ((a)>(b)?(a):(b)) const int INF = 1000000; const int n = 6; int a[n] = {4, 2, 3, 1, 5, 5}; int dp[n]; /* 返回dp陣列中找到>=target且最近的位置 */ int binary_search(int target){ int l = -1, r = n, m; while(l+1 != r){ m = (l+r)/2; if (dp[m] < target) l = m; else r = m; } return r; } /** dp[i]表示長度為i+1的上升子序列中末尾元素的最小值 */ int dp2(){ for (int i = 0; i < n; i++) { dp[i] = INF; } for (int i = 0; i < n; i++) { int p = binary_search(a[i]); dp[p] = a[i]; } return binary_search(INF); } int main(){ printf("%d\n", dp2()); system("pause"); return 0; }