最長上升子序列問題的幾種解法
拿POJ 2533來說。
Sample Input
7
1 7 3 5 9 4 8
Sample Output(最長上升/非降子序列的長度)
4
從輸入的序列中找出最長的上升子序列(LIS)。
這題一看,是一道典型的DP問題(就是動態規劃),可以用dfs,深度優先遍歷來解,如下程式碼:
#include<iostream> #include<stack>usingnamespace std; int n; int* a; stack<int> s; int count=0; int best=0; void dfs(int i) { if(i==n) {if(s.size()>best) best=s.size(); return; } if(s.empty()||a[i]>s.top()) { s.push(a[i]); dfs(i+1); s.pop(); } if(s.size()+n-i-1>best) dfs(i+1); } int main() { while(cin>>n) { int i; a=newint[n]; for(i=0;i<n;i++) { cin>>a[i]; } dfs(0); cout<<best<<endl; delete [] a; } return0; }
其實為了簡化程式碼以及演算法效率,我們可以用資料組來代替遞迴。。。下面我就給出LIS的DP的資料組形式的解法:
(懶得寫了,就拿個別人的程式碼來展示吧)
#include <iostream>#define SIZE 1001usingnamespace std; int main() { int i, j, n, max; /* a[i]表示輸入第i個元素 */int a[SIZE]; /* d[i]表示以a[i]結尾的最長子序列長度 */int d[SIZE];while(cin >> n) { for (i =1; i <= n; i++) { cin >> a[i]; } max =0; for (i =1; i <= n; i++) { d[i] =1; for (j =1; j <= i -1; j++) { if (a[j] < a[i] && d[i] < d[j] +1) //這邊,要注意 d[i] < d[j] + 1這個條件的限制,它是為了在 //連續幾個 d[i]相同時 只加一次 { d[i] = d[j] +1; } } /* 記錄最長子序列 */if (d[i] > max) max = d[i]; } cout << max << endl; } //system("pause");return0; }
(作者的解釋:)令A[i]表示輸入第i個元素,D[i]表示從A[1]到A[i]中以A[i]結尾的最長子序列長度。對於任意的0 < j <= i-1,如果A(j) < A(i),則A(i)可以接在A(j)後面形成一個以A(i)結尾的新的最長上升子序列。對於所有的 0 < j <= i-1,我們需要找出其中的最大值。
DP狀態轉移方程:
D[i] = max{1, D[j] + 1} (j = 1, 2, 3, ..., i-1 且 A[j] < A[i])
解釋一下這個方程,i, j在範圍內:
如果 A[j] < A[i] ,則D[i] = D[j] + 1
如果 A[j] >= A[i] ,則D[i] = 1
其實上面的方法的複雜度都達到了O(n^2)的數量級,有沒有更好的解法呢?
有,用棧。
這個解法不是我想的,從網上學來的,這裡面與大家分享一下。這個演算法的複雜度只有O(nlogn),在有大量資料的情況下,這演算法效率極高。。。
(摘錄原作者的話)
這個演算法其實已經不是DP了,有點像貪心。至於複雜度降低其實是因為這個演算法裡面用到了二分搜尋。本來有N個數要處理是O(n),每次計算要查詢N次還是O(n),一共就是O(n^2);現在搜尋換成了O(logn)的二分搜尋,總的複雜度就變為O(nlogn)了。
這個演算法的具體操作如下(by RyanWang):
開一個棧,每次取棧頂元素top和讀到的元素temp做比較,如果temp > top 則將temp入棧;如果temp < top則二分查詢棧中的比temp大的第1個數,並用temp替換它。 最長序列長度即為棧的大小top。
這也是很好理解的,對於x和y,如果x < y且Stack[y] < Stack[x],用Stack[x]替換Stack[y],此時的最長序列長度沒有改變但序列Q的''潛力''增大了。
舉例:原序列為1,5,8,3,6,7
棧為1,5,8,此時讀到3,用3替換5,得到1,3,8; 再讀6,用6替換8,得到1,3,6;再讀7,得到最終棧為1,3,6,7。最長遞增子序列為長度4。
我想,當出現1,5,8,2這種情況時,棧內最後的數是1,2,8不是正確的序列啊?難道錯了?
分析一下,我們可以看出,雖然有些時候這樣得不到正確的序列了,但最後算出來的個數是沒錯的,為什麼呢?
想想,當temp>top時,總個數直接加1,這肯定沒錯;但當temp<top時呢? 這時temp肯定只是替換了棧裡面的某一個元素,所以大小不變,就是說一個小於棧頂的元素加入時,總個數不變。這兩種情況的分析可以看出,如果只求個數的話,這個演算法比較高效。但如果要求打印出序列時,就只能用DP了。
用該演算法完成POJ2533的具體程式碼如下:
#include <iostream>#define SIZE 1001usingnamespace std; int main() { int i, j, n, top, temp; int stack[SIZE]; while(cin >> n) { top =0; /* 第一個元素可能為0 */ stack[0] =-1; for (i =0; i < n; i++) { cin >> temp; /* 比棧頂元素大數就入棧 */if (temp > stack[top]) { stack[++top] = temp; } else { int low =1, high = top; int mid; /* 二分檢索棧中比temp大的第一個數 */while(low <= high) { mid = (low + high) /2; if (temp > stack[mid]) { low = mid +1; } else { high = mid -1; } } /* 用temp替換 */ stack[low] = temp; } } /* 最長序列數就是棧的大小 */ cout << top << endl; } return0; }
文章出處:http://www.cnblogs.com/dartagnan/archive/2011/08/29/2158247.html