轉載至:http://blog.csdn.net/u013074465/article/details/45442067
最長遞增子序列 http://blog.csdn.net/lisonglisonglisong/article/details/45241965
最長遞增子序列(Longest Increasing Subsequence)是指找到一個給定序列的最長子序列的長度,使得子序列中的所有元素單調遞增。
例如:{ 3,5,7,1,2,8 } 的 LIS 是 { 3,5,7,8 },長度爲 4。
解法一:轉化爲求最長公共子序列
其實可以把 求最長遞增子序列問題 轉化爲 求最長公共子序列的問題。
- 設數組 { 3, 5, 7, 1, 2, 8 } 爲 A
- 對數組 A 排序,排序後的數組爲 B = { 1, 2, 3, 5, 7, 8 }。
- 於是,求數組 A 的最長遞增子序列,就是求數組 A 與數組 B 的最長公共子序列。
最長公共子序列的求法見《動態規劃DP》。本方法的時間複雜度是
解法二:動態規劃法
雖然解法一也是使用動態規劃,但是與解法一不同的是,解法二不進行轉化,而是直接在原問題上採用動態規劃法。
最優子結構:
對於長度爲 N 的數組 A[N]={a0,a1,a2,…,an−1},假設我們想求以ai 結尾的最大遞增子序列長度,設爲L[i],那麼
也就是 j 的範圍是 0 到 i–1。這樣,想求ai 結尾的最大遞增子序列的長度,我們就需要遍歷 i 之前的所有位置 j(0到 i-1),找出A[j]<A[i],計算這些j 中,能產生最大 L[j] 的 j,之後就可以求出L[i]。之後對每一個A[N]中的元素都計算以他們各自結尾的最大遞增子序列的長度,這些長度的最大值,就是我們要求的問題——數組A的最大遞增子序列的長度。
重疊子問題:
根據上述推導式採用遞歸實現的話,有些子問題會被計算很多次。
動態規劃法:
綜上所述,LIS 問題具有動態規劃需要的兩個性質,可以使用動態規劃求解該問題。設數組 A = { 3,5,7,1,2,8 },則:
具體的打表方式如下:
- 初始化對角線爲 1;
- 對每一個 i,遍歷 j(0 到 i-1):
- 若
A[i] <= A[j]
,置 1。 - 若
A[i] > A[j]
,取第 j 行的最大值加 1。
- 若
打完表以後,最後一行的最大值就是最長遞增子序列的長度。由於每次都進行遍歷,故時間複雜度還是 Θ(n2) 。 c
// LIS 的動態規劃方式實現
#include <iostream>
using namespace std;
int getLISLength(int A[], int len) {
//定義一維數組並初始化爲1
int* lis = new int[len];
for (int i = 0; i < len; ++i)
lis[i] = 1;
// 計算每個i對應的lis最大值,即打表的過程
for (int i = 1; i < len; ++i)
for (int j = 0; j < i; ++j) // 0到i-1
if ( A[i] > A[j] && lis[i] < lis[j] + 1)
lis[i] = lis[j] + 1; // 更新
// 數組中最大的那個,就是最長遞增子序列的長度
int maxlis = 0;
for (int i = 0; i < len; ++i)
if ( maxlis < lis[i] )
maxlis = lis[i];
delete [] lis;
return maxlis;
}
解法三:Θ(nlgn)的方案
本解法的具體操作如下:
- 建立一個輔助數組array,依次讀取數組元素 x 與數組末尾元素 top比較:
- 如果 x > top,將 x 放到數組末尾;
- 如果 x < top,則二分查找數組中第一個 大於等於x 的數,並用 x 替換它。
遍歷結束之後,最長遞增序列長度即爲棧的大小。
注意c數組的下標代表的是子序列的長度,c數組中的值也是按遞增順序排列的。這纔可能用二分查找。
數組array[i]存儲的是子序列長度爲i的序列最後一個值(該值是該子序列中最大的元素;如果長度爲i的序列有多個,那麼array[i]存放這類序列最後元素中的最小一個) cop
int getLISLength(int num[], int length) {
vector<int> ivec;
for (int i = 0; i < length; ++i) {
if (ivec.size() == 0 || ivec.back() < num[i])
ivec.push_back(num[i]);
else {
int low = 0, high = ivec.size() - 1;
while (low < high) {
int mid = (low + high) / 2;
if (ivec[mid] < num[i])
low = mid + 1;
else
high = mid - 1;
}
ivec[low] = num[i];
}
}
return ivec.size();
}
特別注意的是:本方法只能用於求最長遞增子序列的長度,輔助數組中的序列不是最長遞增子序列:
-
例一:原序列爲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,3
則最棧輔助數組爲1,3,8。明顯這不是最長遞增子序列!