動態規劃5 最長單增子序列

2022-05-12 07:43:58 字數 3141 閱讀 7291

(lis longest increasing subsequence)給定乙個數列,從中刪掉任意若干項剩餘的序列叫做它的乙個子串行,求它的最長的子串行,滿足子串行中的元素是單調遞增的。

例如給定序列,答案是3,因為和就是長度最長的兩個單增子序列。

處看此題,怎麼做? 萬能的列舉?列舉全部2^n個子序列,找出最長的,固然可以,就是複雜度太高。我們為什麼要列舉呢?因為要知道取了哪些數,其實我們只需要考慮上乙個數和取了幾個數就可以了吧?因為單增的意思是比前乙個數大,我們要加入這個數的時候,只考慮它比之前加入的最後乙個數大就可以了。而最長的意思是數的個數最多,我們只要知道數的總個數就可以了,沒必要知道具體有哪些數。

讓我們嘗試一下用動態規劃的思考辦法。首先設定數列是a1, a2, a3…an,為了方便我們加入一項a0=-∞,後面我們將發現這會給我們帶來極大的方便。int f[i]表示以第i個數結尾的最長單調子串行的長度, 那麼我們看一下加入ai之前的最後乙個數是aj,顯然j < i並且aj < ai,我們有f(i) = f(j) + 1,因為往後面延長了一項嘛。那根據這個式子,我們顯然應該選擇最大的f(j),才能讓f(i)最大。

於是我們有了遞推關係f(i) = max + 1,光有了遞推關係還不夠,初值呢? f(0) = 0,並且我們加入了a0=-∞,這樣對每個i > 0,j總是存在的,大不了就達到下標0了嘛。

偽**:

f[0] = 0;

for i = 1 to n do

f[i] = 0;

for j = 0 to i – 1 do

f[i] = max(f[i], f[j] + 1)

endfor

endfor

顯然這個演算法的時間複雜度是o(n^2),空間複雜度是o(n)。老生常談的問題,如何找到這樣乙個最長的子串行?記錄決策的辦法總是可以的我們記錄一下使得f(i)最大的j。最終結果是max,我們從這個i值一項一項不斷找到前面的j即可……

更好的演算法?

事實上這個題有時間複雜度更低的演算法。仍然以為例子,我們想像考慮5的時候,之前有兩個長度為2的子串行和,那麼哪個更「好」呢?顯然後者更好,因為3比6小,以3結尾的序列更容易在後面接上乙個數。那麼我們記錄到第i個數之前每個長度的單調子串行中「最好」的那個的最後乙個數的大小,考慮把當前這個數接在**就好了。事實上,我們的意思是在每個長度的單調子串行中選乙個代表,這個代表就是其中「最好」的那個(讓最後一項盡可能小),而我們可以歸納的證明,不同長度的「最好」單調子串行的最後一項是隨著長度而單調遞增的。這是因為,我們每次都試圖把乙個數加到它能接的那個最長的子串行後面,其實也是加到了它能加的末尾最大的子串行上。

那麼問題明了了,開始我們只有乙個長度為0的單調子串行,末尾大小認為是-∞。假設目前我們記錄了f[0],f[1],f[2]…f[m]表示目前單調子串行的最長長度是m,我們考慮ai接到**,我們要找到小於ai的最大那一項,我們把它接到那個序列的後面。因為f是單調遞增的,換句話說,我們找到x= max, 把ai接到f[x]後面,得到f[x + 1] = ai,注意這樣的x一定存在,因為f[0] = -∞。如果我們找到的x < m,則我們實際上更新了長度為(x + 1)的子串行的最後一項,因為顯然有f[x + 1] >=  ai,我們把ai換過去,至少不會變差,這也正式我們儲存每個長度「最好」的單調子串行的初衷。如果x == m,則實際上我們把子序列的長度(種類數)擴充套件到了(m + 1)。

最終結果是什麼呢?是f那個列表的長度,也就是最終變化後的m值。

如果我們迴圈乙個乙個地看,這裡就有o(m)的時間複雜度,但是因為有單調性的存在,我們可以利用二分查詢演算法來找到這樣的x,所以這裡時間複雜度是o(logm),因為m<=n,我們這裡可以認為每次找到x的時間複雜度是o(logn),那麼對於每個ai我們都如此做的時間複雜度就是o(nlogn)了。

最後,我們來提供輸入輸出資料,由你來寫一段程式,實現這個演算法,只有寫出了正確的程式,才能繼續後面的課程。

輸入

第1行:1個數n,n為序列的長度(2 <= n <= 50000)

第2 - n + 1行:每行1個數,對應序列的元素(-10^9 <= s[i] <= 10^9)

輸出

輸出最長遞增子串行的長度。
輸入示例

851

6824

510

輸出示例

5
1

deffind(y,l,r):

2if (l==r) and (f[l]

3returnl4

elif l>r:

5return l-1

6else

:7 mid=int((l+r) / 2)

8if f[mid]>=y:

9return find(y,l,mid-1)

10else:11

return find(y,mid+1,r)

12 n=int(input())

13 a=[-10000000000]

14 f=[-10000000000]

15for i in

range(n):

16 x=int(input())

1718

19 m=0

20for i in range(1,n+1):

21 x=find(a[i],0,m)

22 f[x+1]=a[i]

23if x==m:

24 m+=1

2526

print(m)

注意:這裡的二分查詢要留心,如果寫成這樣會死迴圈:

1

deffind(y,l,r):

2if (l==r) and (f[l]

3returnl4

else

:5 mid=int((l+r) / 2)

6if f[mid]>=y:

7return find(y,l,mid-1)

8else:9

return find(y,mid,r)

因為,如果r= l+1且f[l]

動態規劃 最長遞增子串行

給出序列 1 2 3 4 2 5 3 4 a 1 1,a 2 2,a 7 3,a 8 4 求其最長的遞增子串行,以上最長遞增子串行為 1 2 3 4 5 問題細分 初始化條件f 1 1,序列只有1個長度即為1 f 2 a 2 與下標小於2的比較,即a 1 比較,a 2 a 1 因此更新f 2 f 1...

動態規劃 最長遞增子串行

給定乙個無序的整數陣列,找到其中最長上公升子串行的長度 例項 輸入 10,9,2,5,3,7,101,18 輸出 4 解釋 最長的上公升子串行為 2,3,7,101 長度為4說明 可能會有多種最長上公升子串行的和,只需要輸出對應長度即可 演算法的時間複雜度應為o n2 首先,dp陣列的定義如下 dp...

動態規劃 最長遞增子串行

最長遞增子串行是動態規劃中最經典的問題之一,該問題描述的是在乙個已知序列中,取出若干元素 不必連續 組成乙個新的序列,子串行的各個數先後順序保持不變,且對子序列中的任意下標x令dp i 表示以a i 作為末尾的最長遞增子串行的長度。於是,通過設定這麼乙個陣列,最長遞增子串行的長度便是陣列dp中的最大...