最長遞增子序列

轉載至: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》。本方法的時間複雜度是

Θ(nlgn)+Θ(n2)=Θ(n2)

解法二:動態規劃法

雖然解法一也是使用動態規劃,但是與解法一不同的是,解法二不進行轉化,而是直接在原問題上採用動態規劃法。

最優子結構:

對於長度爲 N 的數組 A[N]={a0,a1,a2,,an1},假設我們想求以ai 結尾的最大遞增子序列長度,設爲L[i],那麼

L[i]=max(L[j])+1,1,where j<i and A[j]<A[i]otherwise

也就是 j 的範圍是 0 到 i1。這樣,想求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。明顯這不是最長遞增子序列!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章