最長遞增子序列(Longest Increasing Subsequence)

定義

最長上升子序列(Longest Increasing Subsequence,LIS),在計算機科學上是指一個序列中最長的單調遞增的子序列。

問題描述

給定一個長度爲 N 的數組,找出一個最長的單調自增子序列(不一定連續,但是順序不能亂)。例如:給定一個長度爲 5 的數組{5, 6, 1, 2, 8},則其最長的單調遞增子序列爲 {5,6,8},長度爲 3。

解法

動態規劃

時間複雜度

該方法的時間複雜度爲 O(n^{2})

實現過程

下面我們用一個實例來分析一下動態規劃求解 LIS 的整個過程。假設數組 A 的內容爲 {5, 6, 1, 2, 8}。

1、第一個元素直接設置 LIS 長度爲 1 即可。如下圖所示。

2、第二個元素 6 大於前面所有元素進行比較。5<6,則 LIS[1] = LIS[0]+1 = 2。如下圖所示。

3、第三個元素 1 和前面的所有元素進行比較。1<6,則 LIS 的長度可能爲 1;1<5,則 LIS 的長度可能爲 1;取最大值,LIS[2]=1 。如下圖所示。

4、第四個元素 2 和前面的所有元素進行比較。1<2,則 LIS 的長度可能爲 LIS[2]+1 = 2;6>2,則 LIS 的長度可能爲 1;5>2,則 LIS 的長度可能爲 1;取最大值,LIS[3]=2 。如下圖所示。

5、第五個元素 8 和前面的所有元素進行比較。2<8,則 LIS 的長度可能爲 LIS[3]+1 = 3;8>1,則 LIS 的長度可能爲 LIS[2]+1 = 2;8>6,則 LIS 的長度可能爲 LIS[1]+1 = 3;8>5,則 LIS 的長度可能爲 LIS[0]+1 = 2;取最大值,LIS[4]=3 。如下圖所示。

算法思路

設長度爲 N 的數組爲 {a0,a1, a2, ..., an-1),則假定以 aj 結尾的數組序列的最長遞增子序列長度爲 LIS(j),則 LIS(j) = {max(LIS(i))+1, i<j 且 a[i] < a[j] }。

二分查找

時間複雜度

該方法的時間複雜度爲 O(n*logn)

算法描述

我們可以引入一個新數組 maxV,該數組的特性爲:

長度爲 1 的遞增子序列最大元素的最小值爲 maxV[1];

長度爲 2 的遞增子序列最大元素的最小值爲 maxV[2];

長度爲 LIS[i] 的遞增子序列最大元素的最小值爲 maxV[LIS[i]]。

首先,證明 maxV[] 是遞增的,因此可以使用二分搜索。我們可以用數學歸納法即可證明:若前 k 個元素是遞增的,一定有 maxV[k+1] \geq maxV[k]

證明:假設不成立,則 maxV[k+1] < maxV[k]。

根據 maxV[k+1] 的定義,存在一個長度是 k+1 的 LIS,並且以 maxV[k+1] 爲最大元素。將上述子序列去掉最後一個元素maxV[k+1], 得到長度爲 k,且最大元素 < maxV[k+1] < maxV[k]。

這顯然與 maxV[k] 的定義矛盾。所以假設不成立。

實現過程

我們用一個實例來分析一下二分查找求解 LIS 的整個過程。假設數組 A 的內容爲 {5, 6, 1, 2, 8}。

我們用 LIS[i-1] 表示長度爲 i 的最長遞增子序列末尾的數據。

1、第一個元素直接加入到 LIS 數組中。LIS[0]=5,表示長度爲 1 的 LIS 數組最後一個元素是 5。如下圖所示。

2、第二個元素爲 6,因爲 6>LIS[0],構成遞增,將數字 6 加入到 LIS 數組中,即 LIS[1]=6,表示長度爲 2 的 LIS 數組的末尾是 6。如下圖所示。

3、第三個元素爲 1,1<LIS[2],因此前面一定有一個位置的數據可以換成 1,並且後面的遞增性質不會被破壞。因此我們使用二分查找在 LIS 數組中找到 1 的位置,我們知道 lower_bound 查找的位置爲 0。也就是說 LIS[0] 可以被替換爲 1。如下圖所示。

4、第四個元素爲 2,2<LIS[2],因此前面一定有一個位置的數據可以換成 2,並且後面的遞增性質不會被破壞。因此我們使用二分查找在 LIS 數組中找到 2 的位置,我們知道 lower_bound 查找的位置爲 1。也就是說 LIS[1] 可以被替換爲 2。如下圖所示。

4、第五個元素爲 8,8>LIS[2],構成遞增,將數字 8 加入到 LIS 數組中,即 LIS[2]=8,表示長度爲 3 的 LIS 數組的末尾是 8。如下圖所示。

這樣,我們完成了遍歷,這時候我們可以發現 LIS 數組的小標爲 2,表示我們要求解的 LIS 長度爲 3。

算法思路

將 array[i] 在當前的 maxV[] 數組中進行二分搜索,找到位置 k,maxV[k] < array[i] < maxV[k+1]。

將 array[i] 加入 maxV[] 數組。僅僅影響 maxV[k+1]  (maxV[k+1] = array[i]),而對其他的元素不產生影響。

參考實現

int LIS(int *a, int n) {
    if (n<=0) {
        return 0;
    }

    vector<int> maxV;
    maxV.push_back(a[0]);
    for(int i=1; i<n; ++i) {
        if (a[i] > *maxV.rbegin()) {
            maxV.push_back(a[i]);
        } else {
            *lower_bound(maxV.begin(), maxV.end(), a[i]) = a[i];
        } 
    }
    return maxV.size();
}

 

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