最長上升子序列——O (nlogn)演算法原因解析!為什麼這樣可以求出來!(附帶動態規劃dp + 二分查詢講解)
什麼是最長上升子序列
網上流傳著一個O (nlogn)演算法,大體是這樣的。
模擬一個棧,如果當前的數比棧頂元素大,就要入棧,如果比棧頂元素小,就二分查詢到剛好比當前數大的數,然後進行替換。本例的流程是這樣的。
2 入棧 當前棧:2
5 比2大 入棧 當前棧:2 5
3 比5小 替換5 當前棧:2 3
4 比3大 入棧 當前棧: 2 3 4
1比2小 替換2 當前棧:1 3 4
7比4大 入棧 當前棧:1 3 4 7
6比7小 替換7 當前棧:1 3 4 6
為什麼可以這麼做?為什麼這樣就可以求出最大上升子序列的長度?為什麼求不出最大上升子序列的最大上升序列?相信所有人看完了都會有這樣一個疑問。
其實這個方法很正確,只是這樣的表示形式,把人們都誤導了。其實正確的思維流程應該是這樣的!
初始的2
5
這時是3,比5小,建立一個分支
4只能跟在3後面
1比2小 建立一個分支
7所以的分支後面都可以加
6比7小,建立分支
這樣才是正確的思維流程!你已經成功了一半了!
還有一個大問題,就是為什麼要這樣思考呢?(模擬一個棧,如果當前的數比棧頂元素大,就要入棧,如果比棧頂元素小,就二分查詢到剛好比當前數大的數,然後進行替換。)
需要一個具體例子來分析。當目前是 2 5這個情況的時候,我們遇到了3。首先排除一種做法,3插入到2 5 之間。因為求的是最長上升子序列,而你的序列是253,這樣顯然是不符合題意的。
所以這樣思考,23有沒有可能才是真正的最優序列?所以我們25也有可能,23也有可能(實際上25已經是不可能了)
當前是2-3 2-5,遇到了4,按照我們之前的思維方式,那麼4和5是不是也要比較一下?2-4難道就沒有可能嗎?
但是我們有一個2-3這個序列了,2-3-4這個情況顯然是最優的,所以2-4直接不要了。(印證了 當前的數比棧頂元素大就要入棧 這句話)
最後分析一下1,當前 2-3-4 2-5兩個序列。1比2小,當然存在這樣一種可能1開頭是最好的所以我們如上面的圖片一樣新增了1這個分支(當然本例的資料不太友好,如果我們 當前存在5-6-7,5-8兩個序列呢?你碰到1的時候,完全有可能後面是234啊,所以你1完全有理由成為一個新增的分支)
例子舉到這裡我們就講明白了。
所以演算法就呼之欲出了。
如果你僅僅是求最長長度的需求,直接用網上的方法就可以。
如果你還需要求出這個具體序列來,毫無疑問你採用空間換時間的方法是最好的。新建一個二維陣列,每當遇到要新建分支的情況,就增加一列,簡單方便。具體程式碼我就不寫了,畢竟我當前做的題僅僅是求長度而已,嘿嘿,相信理解原理的你可以輕鬆寫出演算法來。
下面講一下這一題樸素動態規劃的思路。
這一題我們分析出來的dp思路是,dp[i]代表的是包含當前點位的最長上升子序列,意思就是當前求出的最長上升子序列的最後一位,一定要是i位,這一點對動態規劃有了解的人肯定明白,因為這樣才能實現狀態的轉移。
狀態是怎麼轉移的?
開兩個迴圈,不斷用當前的a[i]和之前的數a[k](0<k<i)進行比較,如果比他大,那就是dp[i]=dp[k] + 1,如果相等就是dp[i] = dp[k],如果比他小就只能棄掉了
上程式碼
//樸素dp o n^2 int dynamic(int[] a) { int n = a.length; int[] dp = new int[n]; dp[0] = 1; int max = 1; for (int i = 1; i < n; i ++) { dp[i] = 1; for (int j = 0; j < i; j ++) { if (a[j] < a[i]) { dp[i] = Math.max(dp[j] + 1, dp[i]); } else if (a[j] == a[i]) { dp[i] = Math.max(dp[j], dp[i]); } } max = Math.max(max, dp[i]); } return max; }
二分優化dp,關於二分查詢我覺得太簡單了,就不講了,即使不查閱資料應該也要知道怎麼去實現
//樸素dp優化 二分查詢 模擬棧 -空間換時間 //二分不難不用學就能寫出來,但是要搞明白為什麼要這麼做 int dynamic2(int[] a) { int n = a.length; List<Integer> list = new ArrayList<>(); list.add(a[0]); for (int i = 1; i < n; i ++) { int topIndex = list.size() - 1; if (a[i] > list.get(topIndex)) { list.add(a[i]); } else { int startIndex = 0; int endIndex = topIndex; while (true) {//當end小於start退出 int index = (endIndex + startIndex) / 2; //對本題而言,不存在相等的情況 //當前元素比中間數小 if (list.get(index) > a[i]) { endIndex = index - 1; if (endIndex == startIndex) { if (a[i] < list.get(startIndex)) { list.set(startIndex, a[i]); } else { list.set(index, a[i]); } break; } //如果當前元素比中間數大 } else if (list.get(index) < a[i]) { startIndex = index + 1; if (startIndex == endIndex) { list.set(startIndex, a[i]); break; } } } } } return list.size(); }
相關文章
- 動態規劃:最長上升子序列動態規劃
- 最長上升子序列動態規劃動態規劃
- 動態規劃-最長上升子序列模型動態規劃模型
- 北京大學郭煒-最長上升子序列 動態規劃講解動態規劃
- 動態規劃求解最長上升子序列問題動態規劃
- 【部分轉載】:【lower_bound、upperbound講解、二分查詢、最長上升子序列(LIS)、最長下降子序列模版】
- 以最長公共子序列問題理解動態規劃演算法(DP)動態規劃演算法
- LeetCode 300. 最長上升子序列(Python、動態規劃、貪心演算法)LeetCodePython動態規劃演算法
- 動態規劃-最長公共子序列動態規劃
- 動態規劃——最長公共子序列動態規劃
- 線性dp:最長上升子序列
- 動態規劃(最長公共子序列LCS)動態規劃
- LeetCode 1626. 無矛盾的最佳球隊---【動態規劃】最長上升子序列變換版-->最大上升子序列和LeetCode動態規劃
- 詳解動態規劃最長公共子序列--JavaScript實現動態規劃JavaScript
- 最長公共子序列問題—動態規劃sdut動態規劃
- 線性dp--最長上升子序列變形
- 力扣1143. 最長公共子序列 動態規劃之最長公共子序列力扣動態規劃
- 最長上升子序列
- 動態規劃經典問題----最長公共子序列動態規劃
- 動態規劃求最長降序序列動態規劃
- [線性dp] 合唱隊形(最長上升子序列模型)模型
- 【LeetCode動態規劃#14】子序列系列題(最長遞增子序列、最長連續遞增序列、最長重複子陣列、最長公共子序列)LeetCode動態規劃陣列
- NlogN 求最長不下降子序列(LIS)
- 動態規劃之最長公共子序列求解動態規劃
- 死嗑 最長上升子序列(LIS)
- [動態規劃] 六、最長迴文子串動態規劃
- 線性dp:最長公共子序列
- 淺談最長公共子序列引發的經典動態規劃問題動態規劃
- 最長上升子序列LIS 詳解+變形+擴充
- 動態規劃(DP)動態規劃
- [LeetCode解題] -- 動態規劃二 [ 子串、子序列問題 ]LeetCode動態規劃
- DP筆記最長上升子序列(LIS)以及零件分組問題筆記
- 動態規劃演算法(DP)學習<1>動態規劃演算法
- 圖解 | 原來這就是動態規劃圖解動態規劃
- 最優二叉查詢樹—動態規劃C++動態規劃C++
- 【動態規劃】樹形DP完全詳解!動態規劃
- MySQL查詢為什麼沒走索引?這篇文章帶你全面解析MySql索引
- [動態規劃] 區間 dp動態規劃