【算法知識總結】最長遞增子序列

最長遞增子序列:
找到給定序列的最長子序列的長度,使得子序列所有元素單調遞增。

解法一 轉化爲求最長公共子序列

  • 設數組{3,5,7,1,2,8}爲A
  • 對數組A排序,排序後爲B={1,2,3,5,7,8}。
  • 求A數組的最長遞增子序列,就是求數組A與數組B的最長公共子序列

最長公共子序列求法的時間複雜度:
θ(nlgn)+θ(n2)=θ(n2)

解法二 動態規劃法

與第一個辦法不同的是,動態規劃是直接在原問題上採用動態規劃
最優子結構:
對於長度爲N的數組A[N]={a0,a1,a2,a3,a3,...,an1} ,假設我們想要以ai 結尾的最大遞增子序列長度,設長度L[i] ,那麼

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

也就是j 的範圍是0i1 。這樣,想求ai 結尾的最大遞增子序列的長度,我們就需要遍歷i 之前的所有位置j(0i1) ,找出A[j]<A[i] ,計算這些j 中,能產生最大L[j]j ,之後就可以求出L[i] 。之後對每一個A[N] 中的元素都計算以他們各自結尾的最大遞增子序列長度,這些長度的最大值,就是我們要求的問題數組A 的最大遞增子序列的長度
重疊問題:
根據上面公式的推導採用遞歸實現的話,有一些子問題就會被計算很多次。
動態規劃法:
綜上所述:LIS問題具有動態規劃需要的兩個性質,可以使用動態規劃求解該問題。設數組A={3,5,7,1,2,8} ,則:
這裏寫圖片描述


具體打表方式如下:


  • 初始化對角線爲 1;
  • 對每一個ij0i1

  • A[i]<=A[j] ,置 1。
  • A[i]>A[j] ,取第j 行的最大值加 1。
    打完表以後,最後一行的最大值就是最長遞增子序列的長度。由於每次都進行遍歷,故時間複雜度還是 θ(n2)
//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數組中的值也是按遞增順序排列的。這纔可能用二分查找。

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。明顯這不是最長遞增子序列!

合唱隊問題

問題描述:
計算最少出列多少位同學,使得剩下的同學排成合唱隊形
問題說明:
N 位同學站成一排,音樂老師要請其中的(NK) 位同學出列,使得剩下的K位同學排成合唱隊形。
合唱隊形是指這樣的一種隊形:設K 位同學從左到右依次編號爲12K 他們的身高分別爲T1T2TK 則他們的身高滿足存在i1<=i<=K 使得T1<T2<......<Ti1<Ti>Ti+1>......>TK
任務是:
已知所有N位同學的身高,計算最少需要幾位同學出列,可以使得剩下的同學排成合唱隊形。
輸入:
整數N ,一行整數,空格隔開,N 位同學身高
輸出:
最少需要幾位同學出列
樣例輸入:

8
186 186 150 200 160 130 197 200

樣例輸出:

4

根據題意可知,我們需要求出一個“中間點”,使得其左邊的【最長遞增子序列】和其右邊的【最長遞減子序列】之和最大。

```
#include <iostream>    
#include <vector>    
using namespace std;    

int LonggestIncreaseLength(vector<int> &vec) {    
    vector<int> result(vec.size(), 1);    
    vector<int> result2(vec.size(), 1);    
    for (int i = 1; i < vec.size(); i++) {    
        for (int j = 0; j < i; j++) {    
            if (vec[i] > vec[j] && result[i] < result[j] + 1)    
                result[i] = result[j] + 1;    
        }    
    }    

    for (int i = vec.size() - 2; i >= 0; --i) {    
        for (int j = vec.size() - 1; j > i; --j) {    
            if (vec[i] > vec[j] && result2[i] < result2[j] + 1)    
                result2[i] = result2[j] + 1;    
        }    
    }    
    int max = 0;    
    for (int i = 0; i < vec.size(); i++) {    
        if (max < result[i] + result2[i])    
            max = result[i] + result2[i];    
    }    
    return vec.size() - max + 1;    
}    
int main() {    
    int n;    
    cin >> n;    
    if (n <= 0)    
        return 0;    
    vector<int> ivec(n);    
    for (int i = 0; i < n; i++)    
        cin >> ivec[i];    
    cout << LonggestIncreaseLength(ivec) << endl;    
}    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章