最长上升子序列 LIS(Longest Increasing Subsequence)

引出:

问题描述:给出一个序列a1,a2,a3,a4,a5,a6,a7….an,求它的一个子序列(设为s1,s2,…sn),使得这个子序列满足这样的性质,s1<s2<s3<…<sn并且这个子序列的长度最长。输出这个最长的长度。(为了简化该类问题,我们将诸如最长下降子序列及最长不上升子序列等问题都看成同一个问题,其实仔细思考就会发现,这其实只是<符号定义上的问题,并不影响问题的实质)
例如有一个序列:1  7  3  5  9  4  8,它的最长上升子序列就是 1 3 4 8 长度为4.

分析:

这题目是经典的DP题目,也可叫作LIS(Longest Increasing Subsequence)最长上升子序列 或者 最长不下降子序列。很基础的题目,有两种算法,复杂度分别为O(n*logn)和O(n^2) 。

算法1:
时间复杂度:O(n^2):
我们依次遍历整个序列,每一次求出从第一个数到当前这个数的最长上升子序列,直至遍历到最后一个数字为止,然后再取dp数组里最大的那个即为整个序列的最长上升子序列。我们用dp[i]来存放序列1-i的最长上升子序列的长度,那么dp[i]=max(dp[j])+1,(j∈[1, i-1]); 显然dp[1]=1,我们从i=2开始遍历后面的元素即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Author: Tanky Woo
// Blog: www.WuTianQi.com
int dp[1000];
int LIS(int arr[1000], int n)
{
	for(int i=1; i<=n; ++i)
		dp[i] = 0;
	int ans;
	dp[1] = 1;
	for(int i=2; i<=n; ++i)
	{
		ans = dp[i];
		for(int j=1; j<i; ++j)
		{
			if(arr[i]>arr[j] && dp[j]>ans)
				ans = dp[j];
		}
		dp[i] = ans+1;
	}
	ans = 0;
	for(int i=1; i<=n; ++i)
	{
		if(dp[i] > ans)
			ans = dp[i];
	}
	return ans;
}

算法2:
时间复杂度:(NlogN):
除了算法一的定义之外,增加一个数组b,b[i]用以表示长度为i最长子序列的最后一个数最小可以是多少。易证:i<j时,b[i]<b[j]。
在二分查找时,一直更新b[]内容,设此时b的总长度为k,
若1. arr[i] >= b[k], 则b[k+1] = arr[i];
若2. arr[i] <  b[k], 则在b[1..k]中用二分搜索大于arr[i]的最小值,返回其位置pos,然后更新b[pos]=arr[i]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Author: Tanky Woo
// Blog: www.WuTianQi.com
// num为要查找的数,k是范围上限
// 二分查找大于num的最小值,并返回其位置
int bSearch(int num, int k)  
{  
    int low=1, high=k;  
    while(low<=high)  
    {  
        int mid=(low+high)/2;  
        if(num>=b[mid])  
            low=mid+1;  
        else   
            high=mid-1;  
    }  
    return low;  
}  
 
int LIS()
{
	int low = 1, high = n;
	int k = 1;
	b[1] = p[1];
	for(int i=2; i<=n; ++i)
	{
		if(p[i]>=b[k])
			b[++k] = p[i];
		else
		{
			int pos = bSearch(p[i], k);
			b[pos] = p[i];
		}
	}
	return k;
}

以下是证明b[]的单调递增性:
b序列是严格递增的,即b[1] < b[2] < … < b[t]。
证明:
若b[i] >= b[i + 1],b[i + 1] 是长度为i+1的递增子序列的尾项的最小值,设此序列为x[1]..x[i+1],x[1]..x[i]即构成长度为i的递增子序列,x[i] < x[i+1] = b[i+1] <= b[i],与b[i]定义不符。

最后,给出两个有代表性的题目:

1.HDOJ 1257 最少拦截系统

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1257

解题报告传送门:http://www.wutianqi.com/?p=1841

此题用O(n^2)解法做即可。

2.HDOJ 1025  Constructing Roads In JGShining’s Kingdom

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1025

解题报告传送门:http://www.wutianqi.com/?p=1848

此题数据量较大,所以要用O(NlogN)的解法做。

发布者

Tanky Woo

Tanky Woo,[个人主页:https://tankywoo.com] / [新博客:https://blog.tankywoo.com]

《最长上升子序列 LIS(Longest Increasing Subsequence)》有27个想法

  1. 算法2的第26行,我验证得知,if语句中的等号要去掉,我用文章开端的序列测试,结果如下:
    1 7 3 5 9 4 8
    4
    1 7 3 5 9 4 8 8
    5

    不知道我有没有完全理解你的意思,如果有空你核对一下吧。

发表评论

电子邮件地址不会被公开。 必填项已用*标注