LIS(最長上升子序列)

方法一:

dp動態規劃

狀態設計:dp[i]代表以a[i]結尾的LIS的長度 
狀態轉移:dp[i]=max(dp[i], dp[j]+1) (0<=j< i, a[j]< a[i]) 
時間複雜度:O(N^2) 

例題:https://blog.csdn.net/y201619819/article/details/78354348

 

方法二:貪心+二分

時間複雜度Nlog(N)

a[i]表示第i個數據。 
dp[i]表示表示長度爲i+1的LIS結尾元素的最小值。 
利用貪心的思想,對於一個上升子序列,顯然當前最後一個元素越小,越有利於添加新的元素,這樣LIS長度自然更長。 
因此,我們只需要維護dp數組,其表示的就是長度爲i+1的LIS結尾元素的最小值,保證每一位都是最小值,這樣子dp數組的長度就是LIS的長度。

dp數組具體維護過程同樣舉例講解更爲清晰。 
同樣對於序列 a(1, 7, 3, 5, 9, 4, 8),dp的變化過程如下:

dp[0] = a[0] = 1,長度爲1的LIS結尾元素的最小值自然沒得挑,就是第一個數。 (dp = {1})
對於a[1]=7,a[1]>dp[0],因此直接添加到dp尾,dp[1]=a[1]。(dp = {1, 7})
對於a[2]=3,dp[0]< a[2]< dp[1],因此a[2]替換dp[1],令dp[1]=a[2],因爲長度爲2的LIS,結尾元素自然是3好過於7,因爲越小這樣有利於後續添加新元素。 (dp = {1, 3})
對於a[3]=5,a[3]>dp[1],因此直接添加到dp尾,dp[2]=a[3]。 (dp = {1, 3, 5})
對於a[4]=9,a[4]>dp[2],因此同樣直接添加到dp尾,dp[3]=a[9]。 (dp = {1, 3, 5, 9})
對於a[5]=4,dp[1]< a[5]< dp[2],因此a[5]替換值爲5的dp[2],因此長度爲3的LIS,結尾元素爲4會比5好,越小越好嘛。(dp = {1, 3, 4, 9})
對於a[6]=8,dp[2]< a[6]< dp[3],同理a[6]替換值爲9的dp[3],道理你懂。 (dp = {1, 3, 5, 8})
ok,這樣子dp數組就維護完畢,所求LIS長度就是dp數組長度4。 
通過上述求解,可以發現dp數組是單調遞增的,因此對於每一個a[i],先判斷是否可以直接插入到dp數組尾部,即比較其與dp數組的最大值即最後一位;如果不可以,則找出dp中第一個大於等於a[i]的位置,用a[i]替換之。 
這個過程可以利用二分查找,因此查找時間複雜度爲O(logN),所以總的時間複雜度爲O(NlogN)
 

例題:

LIS 是最長上升子序列。什麼是最長上升子序列? 就是給你一個序列,請你在其中求出一段最長嚴格上升的部分,它不一定要連續。

就像這樣:2, 3, 4, 7 和 2, 3, 4, 6 就是序列 2 5 3 4 1  7  6 的兩個上升子序列,最長的長度是 4。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 9;
int f[N], a[N];
int n;
int find(int l, int r, int x) {//二分查找要插入的位置
	while (l < r) {
		int mid = (l + r) / 2;
		if (f[mid] < x) {
			l = mid + 1;
		}
		else {
			r = mid;
		}
	}
	return l;
}
int lis() {
	int len = 0;
	for (int i = 0; i < n; i++) {
		int k = find(0, len, a[i]);
		f[k] = a[i];
		if (k == len) {
			len++;
		}
	}
	return len;
}
int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d", a + i);
	}
	printf("%d\n", lis());
	return 0;
}

 

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