最长上升子序列&最大上升子序列和

        最长上升子序列问题即是求一个给定数组中,严格递增的最长子序列的长度,如序列1,2,2,4,3。其最长上升子序列为1,2,3or1,2,4.最长子序列的长度即是3。最大上升子序列和问题即是求一个给定数组中,严格递增的子序列中和最大的序列的和(显然,最长子序列未必和是最大的)。如序列100,1,2,3,其最长上升子序列为1,2,3,而最大上升子序列和为100。

       由于DP在求解问题最优解时有巨大优势,所以看到“最长”、“最大”我们就最先想到如何用DP求解这两个问题。之所以把这两个问题放在一起讲,因为不仅其题目本身容易被混淆,而且解法的思路上是相通的。

      求解思路:对最长上升子序列问题,我们创建一个与给定数组A同样大小的数组B,用B[i]保存数组A中以A[i]结尾的上升子序列的长度,很显然B[i+1]=max(1,B[j]+1)(j={0<=j<=i&&A[j]<A[i+1]&&max(B[j])})(即j等于A[i+1]前面比其小的元素中对应子序列长度最长的那个元素的下标)

       对最大子序列和问题,我们类比上面同样创建一个与给定数组A同样大小的数组B用B[i]保存数组A中以A[i]结尾的上升子序列的和,很显然B[i+1]=max(A[i+1],B[j]+A[i+1])(j={0<=j<=i&&A[j]<A[i+1]&&max(B[j])})。对我们ITers来说,代码总会比文字描述更来的直接。

       代码:求最大上升子序列和

#include<stdio.h>
#define MAX 1000
typedef struct node
{
     int num;
     int sum;
}Node;
int main()
{
     int n,i,j,msum,max;
     Node set[MAX];
     while (~ scanf ( "%d" ,&n))
     {
         for (i=0;i<n;i++)
         {
             scanf ( "%d" ,&set[i].num);
             set[i].sum=set[i].num;
         }
         msum=set[0].sum;
         for (i=1;i<n;i++)
         {
             max=0;   //这个初始化勿漏掉!
             for (j=i-1;j>=0;j--)
             {
                 if (set[j].num<set[i].num)
                 {
                     if (set[j].sum>max)
                         max=set[j].sum;
                 }
             }
             set[i].sum+=max;
             if (set[i].sum>msum)
                 msum=set[i].sum;
         }
         printf ( "%d\n" ,msum);
     }
     return 0;
}

上面使用DP求解问题的时间复杂度为O(n^2),下面我们介绍一种求最长上升子序列的更高效的方法,时间复杂度为O(nlogn)。这里我们先给出代码,然后再做分析。
代码:求最长上升子序列,O(nlogn)
#include<stdio.h>
#define MAX 100000
int num[MAX],temp[MAX];
int find( int element, int k)
{
     int left=0,right=k,mid;
     while (left<right)
     {
         mid=(left+right)/2;
         if (temp[mid]>element)
             right=mid;       //注意不能是mid-1,否则可能导致返回的结果不正确,不同与传统意义上的二分查找
         else if (temp[mid]==element)
             return mid;
         else left=mid+1;
     }
     return left;
}
int main()
{
     int n,i,j,k;
     while (~ scanf ( "%d" ,&n))
     {
         for (i=0;i<n;i++)
             scanf ( "%d" ,&num[i]);
         temp[k=0]=num[0];
         for (i=1;i<n;i++)
         {
             if (num[i]>temp[k])
                 temp[++k]=num[i];
             else if (num[i]<=temp[0])
                 temp[0]=num[i];
             else   //temp[0]<num[i]<temp[k]
             {
                 temp[find(num[i],k)]=num[i];   
             }
         }  
         printf ( "%d\n" ,k+1);
     }
     return 0;
}
      这里对上述代码思想进行简要的分析。同DP创建一个临时数组temp,其用于保存访问到num[i]时的最长子序列中的元素(但这里只能保证不同状态下len(temp)是非递减的,并不能保证其中的元素就是访问到num[i]状态时的最长子序列元素),如序列1,2,3,8,4,5,9,1
      当访问到num[0]=1时,temp[]={1},len(temp)=1;
      当访问到num[1]=2时,由于num[1]>temp[k](k是temp中最后元素的下标,其从0开始),temp[++k]=num[1],从而temp[]={1,2},len(temp)=2;
      ...
      当访问到num[3]=8时,同上,temp[]={1,2,3,8},len(temp)=4;
      当访问到num[4]=4时,由于num[4]<temp[k],因此len(temp)不可能变长,我们这里用num[4]替换temp中大于等于num[4]的最小元素,这样使得后面num[4]<num[i]<temp[k](i>4)可以加进temp中,从而保证len(temp)为当前状态下的最长序列的长度。此时有temp[]={1,2,3,4},len(temp)=4;
      ...
      当访问到num[6]=9时,temp[]={1,2,3,4,5,9},len(temp)=6;
      当访问到num[7]=1时,temp[]={1,2,3,4,5,9},len(temp)=6;
      由此,得到最长上升子序列的长度为6.

     分析到这里大家可能又会思考如果要求输出最长上升子序列的各个元素该怎么处理?这里又要用到DP的思路了,设已经用上面的DP思路求得最长上升子序列的长度为maxlen,然后对给定数组B倒着进行遍历,找到B[j]=maxlen时,保存A[j]到新创建的数组C[maxlen-1]中,然后依次保存比A[j]小且B[i]=maxlen-k的元素A[i]到C[maxlen-1-k]中直到找到B[i]=1&&A[i]<A[j](其中k为已经保存的最长子序列元素个数,j={i|B[i]=maxlen-k&&A[i]<A[j]}),最后打印输出数组C。这组操作要进行多次循环,因为可能存在多个j,使得B[j]=maxlen.



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值