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

来源:互联网 发布:python简明教程中文 编辑:程序博客网 时间:2024/05/16 12:49

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

例如序列:{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;elseleft = 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;}

 

原创粉丝点击