最长递增子序列

最长递增子序列

令S是不同整数x1,x2,….,xn的序列。S的递增序列(IS)是序列xi1,xi2,….xik,其中i1<i2<….ik,使得对任意的1<=j<k,有xij<xij+1。S的最长递增序列(LIS)是具有最大长度的递增序列。

问题:给定一个由不同整数组成的序列,求它的最长递增序列。

归纳假设(首次尝试):给定某个长度小于m的序列,知道如何求它的某个最长的递增序列。

归纳基础包含长度为1的序列,结论显然。给定长度为m的序列,我们先对前m-1个元素求得其中的一个LIS,然后考察xm。如果xm比LIS中最后一个归纳的元素大,则xm就能加到LIS的末尾,形成新的LIS,解答完毕。如果xm不大于LIS中最后一个归纳的元素,则问题无法继续解决。例如可能有多个LIS,而xm也许只能使其中一个更长,而这个LIS未必就是由归纳法得到的那个。

导致上述归纳失败的原因是,可能存在多个最长的LIS。再进一步归纳:

归纳假设(第二次尝试):给定某个长度小于m的序列,知道如何求它的所有最长的递增序列。

这个归纳假设也不行,假如xm不能使任何LIS变长,还有可能存在比最长的短1的IS,且xm可以使它变长,从而得到新的LIS。而我们如果想求第二长的IS,就必须求出所有第三长和第四长的IS,如此等等,这充分说明了这个归纳假设过强了。

那是否真的需要所有的LIS吗?我们只想知道xm是否能让其中某个LIS变长,那么能否找到其中最有可能变长的LIS呢?在所有的LIS中,末尾最小的那个数是最有希望的。

归纳假设(第三次尝试):给定某个长度小于m的序列,知道如何求它的某个最长的递增序列LIS,使得其他LIS的末尾的数都比这个LIS末尾的数大。

这个归纳假设也存在于第二次归纳时同样的情形。 现在问题已经很明朗了,不能随意丢弃短的IS,也许其中某个IS就是最终那个LIS的起始序列。

我们用BIS(k)表示长度为k的最可能的递增序列,即以最小数结尾的序列(如果不止一个,则任取其中某个)。把BIS(k)中末尾的数记为BIS(k).last

归纳假设(第四次尝试):给定某个长度小于m的序列,知道如何对任意k<m-1求出BIS(k),如果存在的话。

归纳基础显然成立。给定xm,要求出它可以改变的那些BIS。xm能加长某个BIS(k)当且仅当下列两个条件成立:(1)xm>BIS(k),于是xm可加入BIS(k);(2)xm<BIS(k+1).last,于是BIS(k)末尾加上xm后比BIS(k+1)的可能性更大。算法如下:给定某个xm,依次查看BIS(i).last的值,其中i=s,s-1,s-2等,知道找到某个BIS(j).last小于xm。如果这样的j不存在,那么xm就是当前序列中最小的数,并将其作为BIS(1)。若j=s,则把xm加在BIS(s)后,构造出一个新的BIS(s+1)。否则,有BIS(j).last<xm<BIS(j+1).last,于是用BIS(j+1)替换BIS(j)xm。这个算法还可以进一步优化,由于集合是有序的,所以可以用二叉搜索进行查找。这样,每个xm最多增加O(logm)次比较,整个运行时间为O(nlog n)。现在问题变为,如何设计数据结构来实现上述算法。

参考代码:

int binarysch(const int *arr, int len,int key)
{
	if(arr == NULL || len < 0)
		return -1;

	int left = 0;
	int middle = 0;
	int right = len - 1;

	//为什么要判断=呢?因为可能left、right的值在循环前不等,循环后相等呢,此时还应该处理这个值
	while(left <= right)
	{
		middle = left + (right - left) / 2;
		if(arr[middle] < key)
			left = middle + 1;
		else if(arr[middle] > key)
			right = middle - 1;
	}

	return right;
}

int LIS(int *arr,int length)
{
	if(arr == NULL || length <=0)
		return -1;

	//初始化为0
	int *lenarr = new int(length);
	memset(lenarr,0,length);

	//第一个数,此时长度为1,以它自己结尾
	lenarr[0] = arr[0];

	//记录LIS
	int curlen = 1;

	for(int i = 1; i < length; ++i)
	{
		//直接添到末尾,得到新的LIS
		if(arr[i] > lenarr[curlen-1])
			lenarr[++curlen - 1]=arr[i];
		else
		{
			//查找可能变长的IS
			int pst = binarysch(lenarr,curlen,arr[i]);
			lenarr[pst+1] = arr[i];
		}
	}

	return curlen;
}


int main(int argc, char **argv)
{
	int arr[] = {7,2,3,1,2,4,5,8,9,10,11,6,8,9,10,11};

	int lis = LIS(arr,16);
	
	for(;;);
	return(0);
}

 经过上述分析,可知,当第一次进行归纳时,未必能得到结果,此时需要调整归纳假设;当我们对第i步进行分析时,可能还有很多沉余的信息。例如,假设知道如何求长度小于m的所有IS,现在来考虑m+1,此时需要记录所有已m+1为结尾的IS。那么怎么记录这些信息呢,该用什么样的数据结构呢?之前我就被困在这里,不知道如何记录所有的IS,所以想到算法,还应当能实现出来。最后想到用二维数组来记录,i表示IS长度,j表示当前处理的长度,则[i,1…j]表示构成所有长度为i的末尾的数为j。对这个算法在仔细分析,并不需要记录所有的IS,这就是改进,性能好的算法并不是一步想出来的。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值