算法小題:最長遞增子序列


前言

ok,這兩天又研究了關於最長遞增子序列相關的一些內容。記錄在此,希望便人便己


問題描述

求一個序列的最長遞增子序列,這樣的子序列是允許中間越過一些字符的,即留“空”。
例如:序列A: 4 2 3 1 5 的最長遞增子序列爲 2 3 5,長度爲 3 。

在這裏插入圖片描述


解法一

分析

這題其實和上次那個最大連續子序列和有點像,解法一的思路也和最大連續子序列和問題有些許聯繫。可以採用類似的解空間的思路。
對於給定的一個求解序列來說,假定其最終的答案是b0,b1,b2…bj(這裏的b0,b1,b2…都是從原序列中抽取的遞增值,其中bj是最後一個元素,也是最大的一個),那麼可以肯定bj要麼是a0,要麼是a1,…要麼是a(n-1).也就是說,最終求解的序列的最大值,要麼是以a0爲結尾的序列,要麼是a1結尾的序列…

舉個栗子,還是題目中的那個序列:最終的答案可以是以下幾種:
(1)、以4爲結尾的序列,只有一種:就是4
(2)、以2爲結尾的序列,一種:2 (注意(4 2)不是遞增的)
(3)、以3爲結尾的序列,兩種:3 或者(2 3)

所以我們可以確定一個條件,然後所有的條件都組合起來。還是一句話,這裏並不是說要用暴力解法,全部都列舉出來,只是用這種思路(先確定解空間的一個條件)來構造符合動態規劃的性質和特點,也就是重複子問題和最優子結構。

經過上述的分析,我們可以用lcs[i] 表示 以第i個結點爲遞增序列的最後一個結點的所有序列中最長的序列長度。

說的有點拗口哦,以題目所給的序列作爲例子,比如說lcs[2]就是以3爲結尾的遞增序列中長度最長的那個,以3位結尾的遞增序列有兩種3 和 2 3 ,最長的長度肯定就是2了。所以lcs[2] = 2.

構造了lcs[ ]之後,我們可以得到動態規劃的很重要的一個屬性,就是最優子結構性質。具體一點就是說,lcs[i] 的求解可以從lcs[j],(0<=j <= i-1) 得到,說的在透明些,也就是說,lcs[i]的求解不需要重複i元素之前的所有序列情況,只要用到前面記錄的所有lcs[j] 就可以(0<=j <= i-1) ),這也是動態規劃思想的一個精髓,用記表的方式避免了重複計算相同的單元

最終我們可以得到這樣一個遞推式:
在這裏插入圖片描述

好了遞推式出來了,代碼就不成問題了。

代碼

//利用動態規劃求解最長遞增子序列
int LongIncSeq(int data[],int n)
{
    int lis[n] = {0};
    int longest = 0;

    for(int i = 0;i<n;++i)
    {
        int tmpLon = 0;         //記錄以i爲結束結點的,最長遞增子序列
        for(int j = i-1;j>=0;--j)       //判斷data[0]-data[i-1]所有比data[i]小的元素
        {
            if(data[i] > data[j])
            {
                if(lis[j] >tmpLon)      //取較大的值
                    tmpLon = lis[j];
            }
        }
        lis[i] = tmpLon+1;          //記錄以i爲尾部的最長的遞增子序列
        if(lis[i]>longest)      //記錄整體最長的
            longest = lis[i];
    }
    return longest;
}

注:以上算法的複雜度很容易看出來是O(N2)級別的


解法二

分析

解法二比較巧妙,照我看來,還是從某種程度上用瞭解空間的這種思維方式。它是這樣分解解空間的:

假設遞增子序列B是一個解,那麼B長度可能的取值是
0,1,2,3,…, n-1. 這個挺容易理解的。
那麼我可以在遍歷元素的過程中,記錄着長度爲i的遞增子序列最大元素的最小值。用maxElem[i]表示。

具體來說:
(1)、長度爲1的遞增子序列最大元素的最小值爲maxElem[1]
(2)、長度爲2的遞增子序列最大元素的最小值爲maxElem[2]
(3)、長度爲2的遞增子序列最大元素的最小值爲maxElem[3]
… … …
比如說對於序列A = {1,-1,2,-3,4,-5,6,-7}
當i爲4時,
長度爲1的遞增序列有:1,-1,2,-3 這四個,其中的最小值爲-1
長度爲2的遞增序列有:(1,2),(-1,2),其中的最小值爲2
長度爲3的遞增序列沒有。

與此同時,還要記錄一個遍歷到當前位置時,產生的最大的遞增子序列,也就是隨時更新maxElem[]數組的大小,假設用變量longIncSeq表示。

在遍歷的時候主要的操作是用當前元素A[i] 和maxElem[j](j>=0 && j<=longIncSeq ),進行比較 (當然是從後往前比較)。
如果A[i]大於某個maxElem[j],就說明可以形成一個以A[i]爲最大元素的子序列。如果這個新形成的子序列長度大於longIncSeq,就更新longIncSeq的值。同時要更新對應長度子序列最大元素(也就是末尾元素)的最小值。

我知道,看起來寫的有些複雜,真正動動你的小手指,模擬一下就基本清楚了。

代碼

//另一種動態規劃的解法:最長遞增子序列
int LongIncseq(int data[],int n)
{
    int longIncSeq = 0;         //記錄最長的遞增子序列的長度
    int maxElem[n] = {0};       //記錄長度爲i的遞增子序列最大元素(也就是序列的末尾元素)的最小值

    maxElem[0] = (*min_element(data,data+n))-1;  //最小元素減1
    for(int i = 0;i<n;++i)
    {
        int j;

        for( j= longIncSeq;j>=0;--j)        //尋找所有序列長度在longIncSeq—0之間
        {
            if(data[i] > maxElem[j])       //data[i]大於序列長度爲j的最大元素
            {
                break;
            }
        }

        if(j == longIncSeq)
        {
            ++longIncSeq;

            maxElem[longIncSeq] = data[i];          //最新的序列長度
        }
        else if(maxElem[j]<data[i] && maxElem[j+1] > data[i])    //新長度爲j+1的序列,其最大值小於原來的那個就更換
        {
            maxElem[j+1] = data[i];
        }
    }

    return longIncSeq;
}

總結

稍稍總結一下吧,兩點內容:
(1)、動態規劃算法針對的是問題的求解過程,而不是針對問題本身。我們在有了一個思路之後,可以按照這個思路,探究是否可以使用動態規劃的思想,就我來看就是記表、以空間換時間。
(2)、分解解空間,先假設確定一個條件,或許可以作爲上面的一種探究思路。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章