最长递增子序列

算法动态规划里的经典问题,爱奇艺的编程题,要求时间复杂度为O(n2),空间复杂度为O(1)。首先,先讲一种比较简单的思想,就是先将一个序列排序,然后求两个序列的最长公共子序列,转化为LCS问题。

现在,来解这道题,解法是转载别人的,做一个整合吧。

  • 动态规划:

一个各公司都喜欢拿来做面试笔试题的经典动态规划问题,互联网上也有很多文章对该问题进行讨论,但是我觉得对该问题的最关键的地方,这些讨论似乎都解释的不很清楚,让人心中不快,所以自己想彻底的搞一搞这个问题,希望能够将这个问题的细节之处都能够说清楚。

对于动态规划问题,往往存在递推解决方法,这个问题也不例外。要求长度为i的序列的Ai{a1,a2,……,ai}最长递增子序列,需要先求出序列Ai-1{a1,a2,……,ai-1}中以各元素(a1,a2,……,ai-1)作为最大元素的最长递增序列,然后把所有这些递增序列与ai比较,如果某个长度为m序列的末尾元素aj(j<i)比ai要小,则将元素ai加入这个递增子序列,得到一个新的长度为m+1的新序列,否则其长度不变,将处理后的所有i个序列的长度进行比较,其中最长的序列就是所求的最长递增子序列。举例说明,对于序列A{35, 36, 39, 3, 15, 27, 6, 42}当处理到第九个元素(27)时,以35, 36, 39, 3, 15, 27, 6为最末元素的最长递增序列分别为
    35
    35,36
    35,36,39
    3
    3,15
    3,15,27
    3,6
当新加入第10个元素42时,这些序列变为
    35,42
    35,36,42
    35,36,39,42,
    3,42
    3,15,42
    3,15,27,42
    3,6,42

这其中最长的递增序列为(35,36,39,42)和(3,15,27,42),所以序列A的最长递增子序列的长度为4,同时在A中长度为4的递增子序列不止一个。

该算法的思想十分简单,如果要得出Ai序列的最长递增子序列,就需要计算出Ai-1的所有元素作为最大元素的最长递增序列,依次递推Ai-2,Ai-3,……,将此过程倒过来,即可得到递推算法,依次推出A1,A2,……,直到推出Ai为止,

代码如下

 1 unsigned int LISS(const int array[], size_t length, int result[])
 2 {
 3     unsigned int i, j, k, max;
 4 
 5     //变长数组参数,C99新特性,用于记录当前各元素作为最大元素的最长递增序列长度
 6     unsigned int liss[length];
 7 
 8     //前驱元素数组,记录当前以该元素作为最大元素的递增序列中该元素的前驱节点,用于打印序列用
 9     unsigned int pre[length];
10 
11     for(i = 0; i < length; ++i)
12     {
13         liss[i] = 1;
14         pre[i] = i;
15     }
16 
17     for(i = 1, max = 1, k = 0; i < length; ++i)
18     {
19         //找到以array[i]为最末元素的最长递增子序列
20         for(j = 0; j < i; ++j)
21         {
22             //如果要求非递减子序列只需将array[j] < array[i]改成<=,
23             //如果要求递减子序列只需改为>
24             if(array[j] < array[i] && liss[j] + 1> liss[i])
25             {
26                 liss[i] = liss[j] + 1;
27                 pre[i] = j;
28 
29                 //得到当前最长递增子序列的长度,以及该子序列的最末元素的位置
30                 if(max < liss[i])
31                 {
32                     max = liss[i];
33                     k = i;
34                 }
35             }
36         }
37     }
38 
39     //输出序列
40     i = max - 1;
41 
42     while(pre[k] != k)
43     {
44         result[i--] = array[k];
45         k = pre[k];
46     }
47 
48     result[i] = array[k];
49 
50     return max;
51 }

 

 

该函数计算出长度为length的array的最长递增子序列的长度,作为返回值返回,实际序列保存在result数组中,该函数中使用到了C99变长数组参数特性(这个特性比较赞),不支持C99的同学们可以用malloc来申请函数里面的两个数组变量。

 

  • 贪心+二分查找:

开辟一个栈,每次取栈顶元素s和读到的元素a做比较,如果a>s,  则加入栈;如果a<s,则二分查找栈中的比a大的第1个数,并替换。  最后序列长度为栈的长度。  
这也是很好理解的,对x和y,如果x<y且E[y]<E[x],用E[y]替换E[x],此时的最长序列长度没有改变但序列Q的''潜力''增大。  
举例:原序列为1,5,8,3,6,7  
栈为1,5,8,此时读到3,则用3替换5,得到栈中元素为1,3,8,  再读6,用6替换8,得到1,3,6,再读7,得到最终栈为1,3,6,7  ,最长递增子序列为长度4。 

第二种方法的时间复杂度平均为O(nlogn)。

#include <iostream>
#include <string>
using namespace std;

char sub[10002];
int BinarySearch(char *arr,int len,char c)
{
    int low=0,high=len-1;
    while(low<=high)
{
    int mid=(low+high)>>1;
    if(c==arr[mid])
    return mid;
    else if(c>arr[mid]&&c<arr[mid+1])
    return mid+1;
    else if(c<arr[mid])
    high=mid-1;
    else
    low=mid+1;
}
    return -1;
}

    int main()
{
    int t;
    cin>>t;
    while(t--)
{
    string str;
    cin>>str;
    sub[0]=str[0];
    int size=1,len=str.size();
    for(int i=1;i<len;i++)
{
    if(str[i]>sub[size-1])
    sub[size++]=str[i];
   else
{
    int k=BinarySearch(sub,size,str[i]);
    if(k==-1)
    sub[0]=str[i];
else
    sub[k]=str[i];
}
}
    cout<<size<<endl;
}
    return 0;
}

 

 
posted @ 2015-10-18 17:24  0giant  阅读(638)  评论(0编辑  收藏  举报