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数组的末尾。

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