最长递增子序列(LIS)问题

前言

最长递增子序列(LIS)问题是指,在一个给定的数值序列中,找到一个子序列,使得这个子序列元素的数值依次递增,并且这个子序列的长度尽可能地大。最长递增子序列中的元素在原序列中不一定是连续的。解决最长递增子序列问题的算法最低要求O(nlogn)的时间复杂度。


基本思想

动态规划+二分法

通过一个数组dp[k]来缓存长度为k的递增子序列的最末元素,若有多个长度为k的递增子序列,则记录最小的那个。

首先len=1, dp[0]=seq[0];

然后对seq[i]:若seq[i]>dp[len],那么len++,dp[len]=seq[i];

否则,从dp[0]到dp[len-1]中找到一个j,满足dp[j-1]

最终len即为最长递增子序列LIS的长度。

因为在dp中插入数据是有序并且只需替换不用挪动,因此我们可以使用二分查找,将每一个数字的插入时间优化为O(logn),于是算法的时间复杂度从使用排序+LCS的O(n^2)降低到了O(nlogn)。


例子

假设存在一个序列seq[0...8]=2 1 5 3 6 4 8 9 7,可以看出它的LIS长度为5。

下面试着一步步地找出它:

定义一个数组dp,然后令i=0到8逐个考察序列seq,此外用一个变量len来记录现在LIS的长度。

首先,把seq[0]=2有序地放入dp中,令dp[0]=2,len=1

然后,把seq[1]=1有序地放入dp中,令dp[0]=1,len=1

接着,把seq[2]=5有序地放入dp中,因为seq[2]>dp[0],所以令dp[1]=5,len=2

再来,seq[3]=3,正好在dp[0]到dp[1]之间,替换掉dp[1],令dp[1]=3,len=2

继续,把seq[4]=6有序地放入dp中,因为seq[4]>dp[1],所以令dp[2]=6,len=3

然后,seq[5]=4,正好在dp[1]到dp[2]之间,替换掉dp[2],令dp[2]=4,len=3

接着,把seq[6]=8有序地放入dp中,因为seq[6]>dp[2],所以令dp[3]=8,len=4

再来,把seq[7]=9有序地放入dp中,因为seq[7]>dp[3],所以令dp[4]=9,len=5

最后,seq[8]=7,正好在dp[2]到dp[3]之间,替换掉dp[3],令dp[3]=7,len=5

注意:dp[0..4]=1 3 4 7 9不是LIS,它只是存储的对应长度LIS的最小末尾。


完整代码

#include
using namespace std;

int dp[500];
int seq[500];
int slen;
int LISlen;

int binSearch(int array[], int arraySize, int value)
{
	int left = 0;
	int right = arraySize-1;
	int mid;
	while(leftdp[LISlen-1])
		{
			dp[LISlen++] = seq[i];
		}
		else
		{
			int pos = binSearch(dp, LISlen, seq[i]);
			dp[pos] = seq[i];
		}
	}
	cout<>slen;
	for(int i=0;i>seq[i];
	LIS();
	/* input: 16
	          0 8 4 12 2 10 6 14 1 9 5 13 3 11 7 15
		output: 6  */
	return 0;
}



测试案例

输入

16
0 8 4 12 2 10 6 14 1 9 5 13 3 11 7 15

输出

6

参考

博客-Felix021


你可能感兴趣的:(数据结构与算法)