最长递增子序列(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]<seq[i]<d[j],然后更新dp[j]=seq[i]。

最终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<iostream>
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(left<right)
	{
		mid = (right+left)/2;
		if(array[mid]<=value)
			left = mid+1;
		else
			right = mid-1;
	}
	return left;
}

void LIS()
{
	dp[0] = seq[0];
	LISlen = 1;
	int k = 0;
	for(int i=1;i<slen;++i)
	{
		if(seq[i]>dp[LISlen-1])
		{
			dp[LISlen++] = seq[i];
		}
		else
		{
			int pos = binSearch(dp, LISlen, seq[i]);
			dp[pos] = seq[i];
		}
	}
	cout<<LISlen<<endl;
}

int main()
{
	cin>>slen;
	for(int i=0;i<slen;++i)
		cin>>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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值