1. 程式人生 > >【經典動態規劃問題】最長上升子序列 LIS

【經典動態規劃問題】最長上升子序列 LIS

目錄

 

最長上升子序列:

O(N^2)動態規劃:

O(N*logN):貪心+二分


最長上升子序列:

一個數的序列bi,當b1 < b2 < … < bS的時候,我們稱這個序列是上升的。對於給定的一個序列(a1, a2, …, aN),我們可以得到一些上升的子序列(ai1, ai2, …, aiK),這裡1 <= i1 < i2 < … < iK <= N。

O(N^2)動態規劃:

狀態:dp[i],表示以A[i]為結尾陣列的的最長上升子序列長度

初始化:A[i]的最長上升子序列最小為1,也就是左邊的數都大於等於A[i],最長上升子序列只有A[i]元素本身,dp[i] = 1;

dp[i]表示以A[i]的最長子序列長度,我們現在找倒數第二個元素。

遍歷A[0,...i-1],設其中小於A[i]的元素為A[k,j,z...],倒數第二個元素必定在A[k,j,z]中,dp[i]有幾種可能:

 dp[i] = dp[k]+1;

dp[i ]= dp[j]+1;

dp[i] = dp[z]+1;

取這幾種可能結果中的最大值,即為dp[i]的值。

最後返回的則是dp陣列中的元素最大值。

class LongestIncreasingSubsequence {
public:
    int getLIS(vector<int> A, int n) {
        // write code here
        //dp[i]為A[0...i]中的最長上升子序列的長度
        int dp[501]= {0};
        for(int i = 0;i<n;i++){
            dp[i] = 1;//初始化最長上升子序列的長度為1,即自身
        }
        int ans = 1;//記錄最大值
        for(int i = 1;i<n;i++){
            for(int j = 0;j<i;j++){
            if(A[j]<A[i])
                dp[i] = max(dp[i],dp[j]+1);
            }
            //重要的是最大值
            ans = max(dp[i],ans);
        }
       return ans;
    }
};

這道題還有複雜度更低的做法, 可在面試中作為演算法的優化,同時能夠返回最長上升子序列的元素值。

O(N*logN):貪心+二分

從左到右遍歷陣列,用dp陣列維護當前的最長上升子序列,size變數維護最長上升子序列的長度。

如A={ 2,1,3,4,8,6}

A[0]等於2,dp = {2},size=1;

A [1]等於1,小於dp的末尾元素2,因此讓1把2替換掉,dp = {1},size = 1;長度為1的最長子序列,結尾元素為1顯然比結尾元素為2的序列更容易加"上升元素"

A [2]等於3,大於dp的末尾元素1,因此dp中加入2,dp = {1,2},size = 2;

A [3]等於4,大於dp的末尾元素2,因此dp中加入4,dp = {1,2,4},size = 3;

A [4]等於8,大於dp的末尾元素4,因此dp中加入8,dp = {1,2,4,8},size = 4;

A [5]等於6,小於dp的末尾元素8,大於倒數第二個元素4,因此將6替換8,dp = {1,2,4,6},size = 4,因為長度為4的最長上升子序列,結尾是6會比結尾是8的序列要更有利於新增新元素。

得到最長上升子序列為{1,2,4,6},長度為4;

這裡使用了貪心策略:一個上升子序列,最後一個元素越小,越有利於新增後續比它大的元素。

dp為有序陣列,因此若A[i]小於dp末尾元素,需要找到dp中第一個大於等於A[i]的元素並讓A[i]替換掉它。(較小的值越往前面約好)

注意這裡的二分查詢是求下界的,即>=所查詢物件的第一個位置。

class LongestIncreasingSubsequence {
public:
    int binarysearch(int A[], int n,int item){
        int start = 0;
        int end = n-1;
        while(start<=end){
            int mid = (start+end)/2;
            if(A[mid]<item)
                start =  mid+1;
            else
                end = mid-1;
        }
        return start;
    }
    int getLIS(vector<int> A, int n) {
        // write code here
        //dp[i]為A[0...i]中的最長上升子序列的長度
        int dp[501]= {0};
        int size = 0;
         dp[size++] = A[0];
        for(int i = 1;i<n;i++){
           if(A[i]> dp[size-1])//當前元素大於dp陣列中的最後一個元素
               dp[size++] = A[i];
            else{
                 int firstBig =  binarysearch(dp,size,A[i]);//用二分法找到第一個大於等於A[i]的值,進行替換
                 dp[firstBig] =  A[i];
            }
         
        }
        return size;
    }
};

此外在求陣列中第一個大於等於A[i]的值時,可以使用lower_bound()

lower_bound(int* first,int* last,val);函式在(first,last](左開右閉)區間中進行二分查詢。因此陣列必須是排好序的陣列。

函式返回從first開始的第一個大於或等於val的元素的地址。如果所有元素都小於val,則返回last的地址。

第一個大於等於A[i]的陣列下標為:pos =   lower_bound(dp,dp+size,A[i])-dp

因此  int firstBig = dp[pos];