1. 程式人生 > >淺析最長不下降子序列一題的方法

淺析最長不下降子序列一題的方法

對於最長不下降子序列一題,問題描述如下:

有長度為N的序列:
A1 A2 …..An
求最長不下降子序列:Ai1,Ai2,,,,,Aik, 其中ai1<=ai2<=.....<=aik
求最長不下降子序列的長度 

一看最長不下降子序列,最先想到的肯定是O(n²)的DP解法,當然,這裡還是寫下程式碼,加深理解:

var
        a,f:array[1..1000] of longint;
        n,i,j,ans:longint;
function max(x,y:longint):longint;
begin
        if x>y then exit(x) else exit(y);
end;
begin
        readln(n);
        for i:=1 to n do
                read(a[i]);

        for i:=n-1 downto 1 do
                for j:=i+1 to n do
                        if a[i]<=a[j] then
                        begin
                                f[i]:=max(f[i],f[j]+1);
                                ans:=max(ans,f[i]);
                        end;

        writeln(ans+1);
end.


(以下簡稱最長不下降子序列為LS)

但是,我們知道,其實這種方法是很笨的,因為有很多數明顯不能構成一個最優的序列,我們還拿它去比較,就是很多餘的。

但這不能構成一個最優序列是什麼意思呢?

假設當前構成的LS的長度為3,最後一位的數為7,那如果現在又加進一個數,也可以與前面的數構成長度為3的LS,那麼,到底是以6結尾好?還是7結尾好呢?毋庸置疑。。

當然是6.

當然是最後一位越小越好啊,這樣後面構成LS的長度才有可能更優,根據這一思路,我們就可以大概知道我們到底怎麼去優化LS了,那到底怎們實現呢?

所以我們可以多建一個f陣列表示以長度為i的最長不下降子序列的最後一個數是多少。每次增加一個數x,我們都需要去更新f陣列的值,找到一個剛好的下標i使得f[i]>x,則可把f[i+1]賦值為x.

很明顯這種方法是易證的.

程式碼:

var
        a,f:array[1..100000] of longint;
        n,i,k,len:longint;
function find(len,n:longint):Longint;
var
        l,r,mid:longint;
begin
        l:=1; r:=len;
        while l<=r do
        begin
                mid:=(l+r) div 2;
                if f[mid]=n then exit(mid);
                if f[mid]>n then r:=mid-1 else l:=mid+1;
        end;
        exit(l);
end;
begin
        readln(n);
        for i:=1 to n do
                read(a[i]);

        len:=1;
        f[1]:=a[1];

        for i:=2 to n do
        begin
                k:=find(len,a[i]);
                f[k]:=a[i];
                if k>len then len:=k;
        end;
        writeln(len);
end.


但是,對上面程式做一番思考之後,會發現,此方法只是使用於最長遞增子序列,即所輸入的數不能有相等的數,但我們要探討的是LS,所以,我們可以只需對上述程式做一個當相等的時候暴力求位置即可,但這種方法當最壞情況的時候效率還是有可能接近O(n²)的.

當然,我們也可以對二分改動一下,把L,r更新時有些數會重複的這些情況做相應的改變也可以 .最壞複雜度也是O(n²)