LIS 最長遞增子序列

前言

LIS 即 longest increasing string,最長遞增子序列,可以是不連續的。例如 2 3 5 2 3 4 5 的最長遞增子序列爲{2,3,4,5},長度爲4.
兩種方法可以求出,一種O(n^2)的動態規劃算法,一種是O(nlogn)的二分查找算法。

動態規劃算法

  • 具體思路 : 設置一個dp[i],代表以a[i]爲末位的最長遞增子序列的長度。其狀態轉換方程爲 dp[i] = max(dp[i],dp[j]+1) | a[j]<a[i]
  • 核心代碼:
    fori(i, 0, n){
        dp[i] = 1;
        forj(j, 0, i){
            if (a[j]<a[i] && dp[i]<dp[j]+1) {
                dp[i] = dp[j]+1;
            }
        }
    }

不難理解,遞推出以i爲結尾的LIS長度,最後dp數組裏最大的即a數組LIS的長度。
舉例說明:

i 0 1 2 3 4 5 6 7
a[i] 2 3 4 2 3 4 5 6

當 i = 5 時
j = 0 ,a[j] < a[i], dp[i] = max(1,1+1) = 2;
j = 1 , a[j]<a[i], dp[i]=max(2,2+1) =3;
j = 2 ,a[j] = a[i]
j = 3 ,a[j]<a[i], dp[i] = max(3,1+1) = 3;
j = 4 , a[j]<a[i], dp[i] = max(3,2+1) = 3;
通過這一個例子,大家可以看出,這是一個暴力求出以i爲結尾的LIS長度的算法。

O(nlgn)算法

  • 具體思路 : 以上的算法可以看出,每次確定以i爲末尾的LIS時,都是 j 遍歷一遍 0-i,而這個遍歷是O(n)的,所以可以用二分查找的方法簡化成 lgn的複雜度。
    設定一個數組,每次讀取一個a[i]都與數組的末尾一個數top比較,如果a[i] > top,則把a[i]加入數組末尾,如果a[i] < top,則從棧中找到第一個 大於a[i]的數,替換成a[i]。這樣最後棧的長度就是LIS的長度。
  • 核心代碼:
int find(int l, int r, int x) {						//找出第一個>a[i]的元素,如果沒有,返回l
	while (l < r) {
		int mid = l+(r-l) / 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];							//如果返回的 k = len,則代表把a[i]加入了末尾
		if (k == len) {					//長度+1
			len++;
		}
	}
	return len;
}

舉例說明,依舊是上面的例子 :

i 0 1 2 3 4 5 6 7
a[i] 2 3 4 2 3 4 5 6

可以通過打表看出
在這裏插入圖片描述
其中的 k = len時,len++ ,代表a[i]加入了數組的末尾。每次len都比數組f[n]的長度 多一位,在二分算法裏,如果所有的元素都比a[i]小,那麼k = 新加的一位,a[i] 加入f數組的末尾。

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