今天工做任務比較輕,工做之餘想要從新學習算法。因而準備從DP開始,進行一次學習。全部的概念和問題從leetcode和geeksforgeeks獲取。git
像分治法同樣,DP解決的問題都是能夠分解成不少子問題的。但不一樣的是,DP解決的問題必定是有重複計算部分的。github
以fib數列舉例,簡單的遞歸實現沒有避免重複子問題,致使計算過程當中重複計算的部分很大,從而下降效率。算法
function fib (n) { if (n <= 1) return n return fib(n - 1) + fib(n - 2) }
fib(5) / \ fib(4) fib(3) / \ / \ fib(3) fib(2) fib(2) fib(1) / \ / \ / \ fib(2) fib(1) fib(1) fib(0) fib(1) fib(0) / \ fib(1) fib(0)
這是一種自頂向下的方法數組
// Memoization (Top Down) function fibWithMem (n) { var mem = [] for (var i = 0; i < n + 1; ++i) { mem[i] = null } function _fib (m) { if (mem[m] === null) { mem[m] = m <= 1 ? m : _fib(m - 1) + _fib(m - 2) } return mem[m] } return _fib(n) }
經過預先定義好空數組
mem
,而後不斷更新mem,藉助mem進行重複子問題的優化。app
這是一種自底向上的方法學習
// Tabulation (Bottom Up) function fibWithTab (n) { var tab = [0, 1] if (n <= 1) return tab[n] for (var i = 2; i <= n; ++i) { tab[i] = tab[i - 1] + tab[i - 2] } return tab[n] }
經過預先定義好基線條件的數組
tab
,在運行過程當中,不斷利用tab
計算下一個目標值,達到重複子問題的優化目的,而且去掉了遞歸。測試
參考優化
最短路徑問題能夠用DP解決,由於其具備最優子結構的特性。code
最長路徑問題不能用DP解決,由於其不具備最優子結構的特性。遞歸
function lis (arr, n) { var curMax = 1 function _lis (arr, n) { if (n <= 1) return n var maxHere = 1 var res = 1 for (var i = 1; i < n; i++) { res = _lis(arr, i) if (arr[i - 1] < arr[n - 1] && res + 1 > maxHere) maxHere += 1 } if (curMax < maxHere) curMax = maxHere return maxHere } _lis(arr, n) return curMax }
上述解法會超時,由於只利用到了最優子結構的特性,而沒有進行重複子問題的優化。對於一個長度爲4的測試數據而言,調用的結構圖以下。
lis(4) / | lis(3) lis(2) lis(1) / / lis(2) lis(1) lis(1) / lis(1)
function lisWithTap (arr, n) { var tab = [] for (var m = 0; m < n; m++) { tab[m] = 1 } for (var i = 1; i < n; i++) { for (var j = 0; j < i; j++) { if (arr[j] < arr[i] && tab[i] < tab[j] + 1) { tab[i] = tab[j] + 1 } } } return Math.max.apply(null, tab) }
經過製表,消去了遞歸,而且避免了重複計算相同的子問題。
DP解法的時間複雜度爲O(n^2)
時間複雜度能夠被優化爲O(nlogn)
對問題進行分析,採用維護LIS的思想
function lisFast (arr, n) { if (n <= 1) return n var tail = new Array(n) tail.fill(0) var length = 1 tail[0] = arr[0] for (var i = 1; i < n; i++) { if (arr[i] < tail[0]) { tail[0] = arr[i] } else if (arr[i] > tail[length - 1]) { tail[length] = arr[i] length += 1 } else { tail[findCeilIndex(arr, 0, length - 1, arr[i])] = arr[i] } } return length } function findCeilIndex (arr, left, right, val) { var l = left var r = right while (r - l > 1) { var mid = Math.floor(l + (r - l) / 2) if (arr[mid] >= val) { r = mid } else { l = mid } } return r }
以上全部代碼所有以JS實現,同時還有cpp實現,地址在github,全部文檔和源碼會同步更新