最長上公升子串行O nlogn 演算法核心思想簡述

2021-10-24 20:19:56 字數 3708 閱讀 1208

【題目描述】

給定n個數,求這n個數的最長上公升子串行的長度。

【樣例輸入】

2 5 3 4 1 7 6

【樣例輸出】

下面記錄本人對 「貪心 + 二分」 【o(nlogn)】 演算法的一些理解。

1)介紹解題過程

2)分析解法可行性

不想聽廢話,請直接移步 第四部分 :「4 操作的意義」,演示該操作的效果與意義。

貪心對於乙個上公升子串行,顯然其結尾元素越小,越有利於在後面接其他的元素,也就越可能變得更長。

現建立乙個新陣列 b[ ], 用來存放:考慮到第 i 個元素時,我們所獲得的當前lis。(b 下標從 1 開始)

例如:2 5 3 4 1 7 6

考慮到a[1], b = [2]

考慮到a[5], b = [2,5]

考慮到a[3], b = [2,3]

考慮到a[4], b = [2,3,4]

對於乙個上公升子串行,顯然其結尾元素越小,越有利於在後面接其他的元素,也就越可能變得更長

因此,對於每乙個a[ i ],如果a[ i ] > b[len(b)],就把 a [ i ]接到 b 後面,即 b[++len(b)] = a [ i ] (易得這是求嚴格遞增)。

如果 a[ i ] <= b[len(b)] ,就用 a [ i ] 取更新 b 陣列。

具體方法是,在 b 陣列中找到 「第乙個大於等於」 a [ i ]的元素 b [ j ],用a [ i ]去更新 b[ j ]。

如果從頭到尾掃一遍 b陣列的話,時間複雜度仍是o(n^2)。我們注意到 b陣列內部一定是單調不降的(因為 b 陣列是我們獲得的,當前的lis),所有我們可以二分b陣列,找出第乙個大於等於a[ i ]的元素。二分一次 b陣列的時間複雜度的o(lgn),所以總的時間複雜度是o(nlogn)。

為什麼可以這樣做?

把下標大的元素,放到了下標比它小的元素的前面,不會有問題嗎?

情況1:a[ i ] > b[len(b)]

顯然,直接加到 b 陣列後面,lis 長度 +1.

情況2:a[i] <= b[len(b)]

按照上述做法,應在 b 陣列中找到,第乙個不小於它的元素,並用a[i] 替換之。

例如:假設有

b = [2,4,6,8],

假設當前 a[i] = 5,按條件在 b 中找到元素 『4』 ,並替換之,得:

b = [2,4,5,8]

證明替換的可行性:

可以看到,替換之後,只有被替換的位置的元素值改變了,剩餘的並沒有受到影響,並且,b 依舊保持了遞增排序,且長度沒有改變,這是最重要的一點。

我們要求的是 最長上公升子串行 的最大長度 => 即 b 的最大長度。

在遍歷 a 陣列的過程中, b 表示 當前獲得的 lis, b 的長度即為當前的 最長上公升子串行的長度。既然替換之後,b 的長度沒有變,=> 當前最長上公升子串行的長度沒有變 => 沒有改變子問題的結果(雖然b 儲存的不是真正的 lis 序列,但我們最後要得到的是乙個 長度,b 變化只要不改變長度,也就不會改變答案)。

b 陣列發生替換之後,其又有了新意義: b 的長度表示,曾經一定有乙個合法的序列 x = ,使得 遍歷到當前位置時的lis 的長度 len(b) = len(x)。也就是說,b 的長度,一定是由乙個合法的狀態推過來的。

以上證明了,進行替換不影響最終結果。

可這樣替換有什麼意義呢?

接著上述例子

b = [2,4,5,8,10]

現在假設 a = […6,7,8…]

①開始判斷 a 中的元素 『6』, 按照上述做法,6 < b[len(b) = 5] = 10 ,(10是插入 b 的門檻);比 6 大的有: 8 、10,則替換 8,得:

b = [2,4,5,6,10]

②開始判斷 a 中的元素 『7』, 按照上述做法,7 < b[len(b) = 5] = 10 ,(10是插入的門檻),比 6 大的有: 10,則替換 10,得:

b = [2,4,5,6,7]

③開始判斷 a 中的元素 『8』, 按照上述做法,8 > b[len(b) = 5] = 7 ,(7是插入的門檻),則插入,得:

b = [2,4,5,6,7,8]

注意,通過 『6』 的替換,本來比 『7』大的有 『8』

『10』 ,替換成 『6』 後,比 『7』大的只有 '10』了,那麼 『7』 替換 『10』後,插入 b 陣列的門檻由 『10』 變成了 『7』。如果前面不替換 『6』,則 『7』 只能替換 『8』,不能替換 『10』,則沒有降低門檻。

再來看,如果在①②中,不做替換:

①開始判斷 a 中的元素 『6』, 按照上述做法,6 < b[len(b) = 5] = 10 ,無法插入,保持不變,得

b』 = [2,4,5,8]

②開始判斷 a 中的元素 『7』, 按照上述做法,7 < b[len(b) = 5] = 10 ,無法插入,保持不變,得

b』= [2,4,5,7]

②開始判斷 a 中的元素 『8』, 按照上述做法,7 < b[len(b) = 5] = 10 ,無法插入,保持不變,得

b』= [2,4,5,7]

顯然 len(b』) < len(b)

通過觀察可以發現,經過我們的替換,將更小的數替換掉 b 陣列中第乙個不小於它的數(即替換掉不改變 b 遞增性質的數),使得,在判斷後續元素過程中,本來無法加入 b 陣列的元素,經前面的替換操作後,滿足了插入的條件,可以插入。因為插入可以使得 b 陣列的長度增長,而替換不改變 b 的長度,所以我們要盡可能的創造出後續元素可插入的條件。

那麼這個替換操作,其實就是創造這個條件。

用新數去更新前邊的元素,這個元素可能不是最優解的一部分,但是它可以使得後面還未加入的、比較小的數更有可能進入這個陣列

b。通俗地來說,作為門檻,他本來要大於當前序列的最後乙個數才能加進去;就是如果我太大了,我就乖乖呆在末尾;如果前面有乙個數比我大,也就是我比你好,既然我在你後面也就是我們兩者只能選其一,那我只好把你替換掉了。雖然我這臨時臨頭換的不一定最合適,但是對於後面還有很多的人等著排進來的情況下,我給他們創造了更多機會,使得這個序列的最後乙個數有可能變小,讓更多的人進來。

int a[max]

;//a存放原始陣列,

int dp[max]

;//maxlen存放以該元素為中點的最長子序列的長度

int b[max]

;int blen;

intmain()

//初始化

// 複雜度 o(n`2)

for(

int i =

1; i <= n;

++i)

for(

int j =

1; j < i ;

++j)

cout<<

*max_element

(dp+

1,dp+

1+n)

for(

int i =

1;i <= n;

++i)

} cout<}

參考部落格:

最長上公升子串行O NlogN 演算法

給出乙個長度為n的序列,請求出其最長上公升子串行的長度。首先很容易想到o n2 的演算法 f i 表示1 i最長上公升子串行長度則 f i m ax 1 1 j a j i 顯然我們需要的只是滿足 1 j a j i 這一條件的最大f j 那麼我們為何不將他記錄下來呢 用g i 表示f j i 時的...

最長上公升子串行 O nlogn

題目描述 input 輸入乙個整數n 表示接下來有 n 個數輸入。output 輸出當前數列最長上公升子串行的長度。直接上 最長下降子串行 最長山峰序列都可以以該問題為母問題 進行延伸。include include using namespace std define max 1000 int s...

最長上公升子串行O nlogn

假設已經計算出的兩個狀態a和b滿足a a a b 且d a d b 則對於後續所有的狀態i 即i a且i b 來說,a並不會比b差 如果b滿足a b a i 的條件,a肯定也滿足,且二者的d值相同 但反過來卻不一定了,a滿足a a a i 的條件時,b卻不一定滿足。換句話說,如果我們只保留a,一定不...