求数组中最长递增子序列

编程之美有一道关于数组中最长递增子序列,题目如下:
写一个时间复杂度尽可能低的程序,求一个一维数组(N个元素)中最长递增子序列的长度。
例如在序列1,-1,2,-3,4,-5,6,-7中,其最长的递增子序列的长度为4(如1,2,4,6),从该书给的例子我们可以知道的是其最长的递增子序列可以不连续的。
作者利用动态规划方法给了三种解法。
解法一:
根据无后效性的定义,各阶段按照一定的次序排列好之后,对于某个给定阶段的状态来说,它以前各阶段的状态无法直接影响它未来的决策,而只能间接地通过当前状态来影响,如以下目标串:
1,-1,2,-3,4,-5,6,-7
当i=1时,显然,最长的递增序列为(1),序列长度为1.
当i=2时,由于-1<1,因此,必须丢弃第一个值然后重新建立串,当前的递增序列为(-1),长度为1.
当i=3时,由于2>1,2>-1,因此,最长的递增序列为(1,2),(-1,2),长度为2,在这里,2前面是1还是-1对求出后面的递增序列没有直接影响。
假设在目标数组array[]的前i个元素中,最长递增子序列的长度为LIS[i],那么用动态规划可以推出以下公式:
LIS[i+1]=max{1,LIS[k]+1},array[i+1]>array[k],for any k<=i
即如果array[i+1]大于array[k],那么第k+1个元素可以接在LIS[k]长的子序列后面构成一个更长的子序列,同时array[i+1]本身至少可以构成一个长度为1的子序列。
从作者的分析角度,我们可以用树的结构来分析,如图:
这里写图片描述
代码实现:

#include <iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int  Max(int *a,int len)
{
   int max=1;
   for(int i=0;i<len;i++)
      if(max<a[i])
         max=a[i];
   return max;
}
int LIS(int *a,int len)
{
   int *LISAry=new int[len];
   for(int i=0;i<len;i++)
   {
      LISAry[i]=1;
      //遍历寻找最长递增序列,找到后若比当前i的最长递增序列大,就更改
      for(int j=i-1;j>=0;j--)
         if(a[i]>a[j]&&LISAry[i]<LISAry[j]+1)
            LISAry[i]=LISAry[j]+1;
   }
   return Max(LISAry,len);
}
int main()
{
   int a[8]={1,-1,2,-3,4,-5,6,-7};
   int len=sizeof(a)/sizeof(int);
    cout <<LIS(a,len)<< endl;
    //随机测试
    for(int i=0;i<len;i++)
    {
       for(int j=0;j<len;j++)
       {
          a[j]=rand()%100+1;
          cout<<a[j]<<"\t";
       }
       cout<<endl<<"result:  "<<LIS(a,len)<<endl;
    }
    return 0;
}

整个算法的时间复杂度为O( N2 +N)=O( N2 ).
解法二:
第二种解法:
对于前面i个元素的任何一个递增子序列,如果这个子序列的最大元素比array[i+1]小,那么就可以将array[i+1]加载这个子序列后面构成一个新的递增子序列,假设在数组的前i个元素中,以array[i]为最大元素的最长递增子序列的长度为LIS[i],同时假设:
长度为1的递增子序列最大元素的最小值为MaxV[1].
长度为2的递增子序列最大元素的最小值为MaxV[2].
………
长度为LIS[i]的递增子序列最大元素的最小值为MaxV[LIS[i]].
根据作者分析可以得到如下的公式:
LIS[i+1]=max{1,K+1},array[i+1]>MaxV[k],k<=maxLISlen,其中maxLISlen表示当前状态最长递增序列的长度
MaxV[len]=min{array[ i1 ],array[ i2 ],……,array[ ik ]},其中 ik <=N,并且满足LIS[ i1 ]=LIS[ i2 ]=……..=LIS[ ik ]=len.
代码实现:

#include <iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int Min(int *a,int len)
{
   int min=0;
   for(int i=0;i<len;i++)
      if(a[i]<min)
         min=a[i];
}
int LIS(int *a,int len)
{
   //存储对应递增序列长度的最大值的最小元素
   int *MaxV=new int[len+1];
   MaxV[1]=a[0];//数组中的第一值,边界值
   MaxV[0]=Min(a,len)-1;//数组中的最小边界值
      //存储对应索引的序列长度
   int *LISAry=new int[len];
   for(int i=0;i<len;i++)
      LISAry[i]=1;
   int nMaxILS=1;//数组最长递增序列的长度
   for(int i=1;i<len;i++)
   {
      int j;
     //利用穷举从后向前寻找第一个小于a[i]元素的索引
      for(j=nMaxILS;j>=0;j--)
         if(a[i]>MaxV[j])
            {
               LISAry[i]=j+1;
               break;
            }
      //如果当前递增序列大于最长的递增序列,则更新最长序列
      if(LISAry[i]>nMaxILS)
      {
         nMaxILS=LISAry[i];
         MaxV[LISAry[i]]=a[i];
      }
      //改变该递增序长度的最大元素的值,使其在满足
       //同样的递增序列长度情况下,挑选最小元素为最大元素
      else if(MaxV[j]<a[i]&&a[i]<MaxV[j+1])
      {
         MaxV[j+1]=a[i];
      }
   }
   return nMaxILS;
}
int main()
{
   int a[8]={1,-1,2,-3,4,-5,6,-7};
   int len=sizeof(a)/sizeof(int);
    cout <<LIS(a,len)<< endl;
    for(int i=0;i<len;i++)
    {
       for(int j=0;j<len;j++)
       {
          a[j]=rand()%100+1;
          cout<<a[j]<<"\t";
       }
       cout<<endl<<"result:  "<<LIS(a,len)<<endl;
    }
    return 0;
}

整个算法过程中也是O( N2 ).
第三种解法:
根据上面的第二种解法思路,可以得到以下推论:
在递增序列,如果i

#include <iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int Min(int *a,int len)
{
   int min=0;
   for(int i=0;i<len;i++)
      if(a[i]<min)
         min=a[i];
}
int LIS(int *a,int len)
{
   //存储对应递增序列长度的最大值的最小元素
   int *MaxV=new int[len+1];
   MaxV[1]=a[0];//数组中的第一值,边界值
   MaxV[0]=Min(a,len)-1;//数组中的最小边界值
   //存储对应索引的序列长度
   int *LISAry=new int[len];
   for(int i=0;i<len;i++)
      LISAry[i]=1;
   int nMaxILS=1;//数组最长递增序列的长度
   for(int i=1;i<len;i++)
   {
      if(a[i]>MaxV[nMaxILS])
      {
         nMaxILS++;
         MaxV[nMaxILS]=a[i];
         LISAry[i]=nMaxILS;
      }
      else
      {
         int low=1,height=nMaxILS;
         //利用二分法寻找从前向后第一个大于a[i]元素的索引
         while(low<=height)
         {
            int mid=low+(height-low)/2;
            if(a[i]>=MaxV[mid])
            {
               low=mid+1;
            }
            else
            {
               height=mid-1;
            }
         }         
         LISAry[i]=low;
         //更新递增序列长度为low其最大元素改为更小的a[i]
         MaxV[low]=a[i];
      }
   }
   return nMaxILS;
}
int main()
{
   int a[8]={1,-1,2,-3,4,-5,6,-7};
   int len=sizeof(a)/sizeof(int);
    cout <<LIS(a,len)<< endl;
    for(int i=0;i<len;i++)
    {
       for(int j=0;j<len;j++)
       {
          a[j]=rand()%100+1;
          cout<<a[j]<<"\t";
       }
       cout<<endl<<"result:  "<<LIS(a,len)<<endl;
    }
    return 0;
}

整个算法过程时间复杂度是O(N* log2 N).

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值