最长上升子序列O(nlogn)算法

上个月参加腾讯校园招聘的笔试,填空部分有一道题问:计算最长上升子序列的最快算法
的时间复杂度和空间复杂度是多少?

例如序列:{1 4 2 3 7 6 5 7}的最长上升子序列是{1 2 3 6 7},长度为5。

此题的答案是O(nlogn)和O(n),在这之前我只了解n^2的算法,没有看过nlogn的算法,到网上搜索
发现对于该算法的介绍都比较晦涩难懂,因此我决定弄懂它后写一篇blog,详细介绍该算法,让他尽量
容易理解。

为了便于描述,假设序列存放在num数组中。

对于该问题,我们很快就能找到一个简单算法。
定义数组length,lenth[i]表示以下标为i元素结尾的最长上升子序列的长度。
则可以想到如下的转换关系 length[i] = max(length[j])+1,其中0<=j<i并且num[i]>=num[j],用程序实现
如下:

int cal(const int *num ,int size)
{
	int *length ,i ,j ,r ,maxl = 1;
	length = (int*)malloc(size<<2);
	for(i = 0 ;i < size ;i ++)
	{
		r = 0;
		for(j = 0 ;j < i ;j ++)
			if(num[j] <= num[i] && length[j] > r)
				r = length[j];
		length[i] = r + 1;
		maxl = r+1 > maxl ? r+1 : maxl;
	}
	free(length);
	return maxl;
}

上述算法的时间复杂度是O(n^2)的,空间复杂度是O(n)
怎么在O(nlogn)的复杂度下求解最长上升子序列问题呢?
我们知道一般O(n^2)的算法,加入二分的思想后大多数都可以优化到O(nlogn),所以如何在上述算法中利用二分是一个关键。

我们定义一个数组minv,minv[i]表示长度为i的子序列中最大元素的值,该数组一定有如下性质:
对于任意i>j ,一定有minv[i] >= minv[j],也就是说minv一定是升序的。
证明:长度为i的上升子序列中的最后一个元素minv[i]必然大于该序列中的任意一个元素,因为i>j,所以一定有minv[i]>minv[j]。

对于题目中的序列,它对应的minv数组为:{1,2,3,5,7}。
假如现在要在原序列的末尾加上一个10,则minv数组变为{1,2,3,5,7,10}。上述过程不难看出来,如果添加一个数x后再求minv数组,
就是在原来的minv数组中查找位置最靠后并且值小于等于x的元素,设该元素的下标为k,则x和长度为k的子序列可以组成长度为k+1的子序列,
然后更新minv[k+1] = min(x ,min[k+1])即可。
由于minv是有序的,因此上面的过程可以利用二分查找,从而使时间复杂度降至O(nlogn),用程序实现如下:

int cal(const int *num ,int size)
{
	int i ,len=1 ,*minv;
	int left ,right ,mid ,pos;
	minv = (int*)malloc(size<<2);
	minv[0] = num[0];

	for(i = 1 ; i < size ;i ++)
	{
		left = 0 ;
		right = len - 1;
		while(left<=right)
		{
			mid = (left+right)>>1;
			if(minv[mid] > num[i])
				right = mid - 1;
			else
				left = mid + 1;
		}
		pos = right;
		if(pos == len-1)
		{
			minv[len++] = num[i];
		}
		else
		{
			minv[pos+1] = num[i] < minv[pos+1] ? num[i] : minv[pos+1];
		}
	}

	free(minv);
	return len;
}

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值