【題目描述】
給定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)
/ 複雜度 o(nlogn)
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,一定不...