今天工做任务比较轻,工做之余想要从新学习算法。因而准备从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,全部文档和源码会同步更新