定義
最長上升子序列(Longest Increasing Subsequence,LIS),在計算機科學上是指一個序列中最長的單調遞增的子序列。
問題描述
給定一個長度爲 N 的數組,找出一個最長的單調自增子序列(不一定連續,但是順序不能亂)。例如:給定一個長度爲 5 的數組{5, 6, 1, 2, 8},則其最長的單調遞增子序列爲 {5,6,8},長度爲 3。
解法
動態規劃
時間複雜度
該方法的時間複雜度爲 。
實現過程
下面我們用一個實例來分析一下動態規劃求解 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] }。
二分查找
時間複雜度
該方法的時間複雜度爲 。
算法描述
我們可以引入一個新數組 maxV,該數組的特性爲:
長度爲 1 的遞增子序列最大元素的最小值爲 maxV[1];
長度爲 2 的遞增子序列最大元素的最小值爲 maxV[2];
…
長度爲 LIS[i] 的遞增子序列最大元素的最小值爲 maxV[LIS[i]]。
首先,證明 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();
}