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

来源:互联网 发布:最流行的网络语言 编辑:程序博客网 时间:2024/05/19 01:09
最长上升子序列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],用程序实现
如下:

[cpp] view plaincopy
  1. int cal(const int *num ,int size)  
  2. {  
  3.     int *length ,i ,j ,r ,maxl = 1;  
  4.     length = (int*)malloc(size<<2);  
  5.     for(i = 0 ;i < size ;i ++)  
  6.     {  
  7.         r = 0;  
  8.         for(j = 0 ;j < i ;j ++)  
  9.             if(num[j] <= num[i] && length[j] > r)  
  10.                 r = length[j];  
  11.         length[i] = r + 1;  
  12.         maxl = r+1 > maxl ? r+1 : maxl;  
  13.     }  
  14.     free(length);  
  15.     return maxl;  
  16. }  

上述算法的时间复杂度是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 ,minv[k+1])即可。
由于minv是有序的,因此上面的过程可以利用二分查找,从而使时间复杂度降至O(nlogn),用程序实现如下:

[cpp] view plaincopy
  1. int cal(const int *num ,int size)  
  2. {  
  3.     int i ,len=1 ,*minv;  
  4.     int left ,right ,mid ,pos;  
  5.     minv = (int*)malloc(size<<2);  
  6.     minv[0] = num[0];  
  7.   
  8.     for(i = 1 ; i < size ;i ++)  
  9.     {  
  10.         left = 0 ;  
  11.         right = len - 1;  
  12.         while(left<=right)  
  13.         {  
  14.             mid = (left+right)>>1;  
  15.             if(minv[mid] > num[i])  
  16.                 right = mid - 1;  
  17.             else  
  18.                 left = mid + 1;  
  19.         }  
  20.         pos = right;  
  21.         if(pos == len-1)  
  22.         {  
  23.             minv[len++] = num[i];  
  24.         }  
  25.         else  
  26.         {  
  27.             minv[pos+1] = num[i] < minv[pos+1] ? num[i] : minv[pos+1];  
  28.         }  
  29.     }  
  30.   
  31.     free(minv);  
  32.     return len;  
  33. }  
0 0
原创粉丝点击