关于最长不下降子序列的优化

32 篇文章 0 订阅
16 篇文章 0 订阅

概念

一个经典问题。
有长度为N的序列:
A1 A2 …..An
求最长不下降子序列:Ai1,Ai2,,,,,Aik, 其中ai1<=ai2<=…..<=aik
求最长不下降子序列的长度
例如:(1 2 3 3 4 5) 为一个不下降子序列。


O(N^2)

最容易想到的方法,设F[i]表示以第i位为结尾时最长不下降子序列的长度。
F[i]=max(F[j])+1 (a[i]>=a[j])

代码

var
        a:array[1..100] of longint;
        f:array[1..100] of longint;
        n,i,j,ans:longint;
begin
        readln(n);
        for i:=1 to n do
        begin
                read(a[i]);
                f[i]:=1;//初始化,每个位置的初始长度都为1
        end;
        ans:=1;
        for i:=2 to n do
        begin
                for j:=1 to i-1 do
                if (a[i]>=a[j]) and (f[i]<=f[j]) then//如果符合要求且长度更长(等同于f[i]<f[j]+1,避免重复拖慢时间)
                f[i]:=f[j]+1;
                if f[i]>ans then//更新答案
                ans:=f[i];
        end;
        writeln(ans);
end.

Temp数组优化

O(N^2)在数据较大时会超时。

设Temp[i]表示长度为i最长不下降子序列末尾的最小值,ans表示其长度。
每加入一个新数a[i]时,根据数的大小来进行处理。

①a[i]大于等于Temp[ans]
可以直接把a[i]放在第ans+1位。

②a[i]小于Temp[ans]
a[i]不能直接放在列首,但是如果直接丢掉未免太浪费。

试想一下,加入有一列数要加入最长不下降子序列,那么是序列里的数小好加还是序列里的数大好加?
为了让最终结果尽量大,序列中的数就要尽量小。

因为不能放在列首,所以只能用a[i]替换掉Temp中的一个数。
(1).这个数一定要比a[i]大。(否则就没有意义了)
(2).这个数一定是第一个比a[i]大的数。(否则就不是最长不下降子序列)

满足这两个要求,就可以通过维护Temp数组来计算最终答案。

代码

var
        a:array[1..100] of longint;
        temp:array[0..100] of longint;
        n,i,j,ans:longint;
begin
        readln(n);
        for i:=1 to n do
        read(a[i]);
        ans:=1;
        temp[0]:=maxlongint;
        temp[1]:=a[1];   //初始化
        for i:=2 to n do
        begin
                if a[i]>=temp[ans] then  //如果大于最大数
                begin
                        inc(ans);  //放在第ans+1位
                        temp[ans]:=a[i];
                end
                else
                begin
                        for j:=1 to ans do    //从小到大找第一个大于a[i]的Temp[j]
                        if a[i]<temp[j] then
                        begin
                                temp[j]:=a[i];   //替换
                                break;
                        end;
                end;
        end;
 writeln(ans);
end.

O(NlogN)

其实很简单,只要把上面的循环j改成二分查找就行了。

代码

var
        a:array[1..100] of longint;
        temp:array[0..100] of longint;
        n,i,j,ans,l,r,mid:longint;
begin
        readln(n);
        for i:=1 to n do
        read(a[i]);
        ans:=1;
        temp[0]:=maxlongint;
        temp[1]:=a[1];
        for i:=2 to n do
        begin
                if a[i]>=temp[ans] then
                begin
                        inc(ans);
                        temp[ans]:=a[i];
                end
                else
                begin
                        l:=1;
                        r:=ans;
                        mid:=(l+r) div 2;
                        while l<r do  //二分查找j
                        begin
                                if a[i]>=temp[mid] then l:=mid+1
                                else
                                r:=mid;
                                mid:=(l+r) div 2;
                        end;
                        temp[mid]:=a[i];
                end;
        end;
        writeln(ans);
end.

参考:http://blog.csdn.net/fengyingjie2/article/details/54971432

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值