什么是动态规划
动态规划是应该不能叫 一种算法
,而应该叫 一类算法
或者 说是 一种思想
。它和 二分查找
这种算法是不同的,二分查找我们可以用代码表示出来,并且解决所有问题的思路几乎都是一样的。而动态规划其实无法用代码表示出来,每一种问题的解决方法可能用代码写出来都不一样,所以只看动态规划的定义是很难理解的,需要拿多个题目联系才能真正理解。
动态规划的特点就是把一个大的问题分解为若干个小问题,并且这些小问题之间,每一个小问题都可通过前面的小问题计算出来。通常这些小问题其实都是一个具体的状态。如果一个问题可用动态规划解决,那么需要满足如下特点:
- 该问题可以分解为
dp[0], dp[1], … , dp[n]
个状态,最终的答案是其中一个状态,一般是dp[n]
。(或者dp[n][m] 等更多维度,我们这里只拿一维来说) - 其中
dp[i]
可以通过dp[0] ~ dp[i-1]
或者 `dp[i+1]~dp[n] 计算出来。
如果只看定义的话,会发现DP一定是可以通过分治法的方式来解决的。那么为什么还需通过DP算法来解决呢,因为很多时候分治法算法会有大量的重复计算,而动态规划由于记录了状态,时间复杂度会有大幅下降。很多时候时间复杂度可以从 O(2^n)
降低到 O(n^2)
。如果分治法加上一个Cache来缓存已经计算过的节点,那么他的时间复杂度和DP就是一样的了,其实 DP = DC + Cache
。
从上面的两个特点就可以看出来,DP 问题的难点有两个:
- 如果定义
dp[i]
,即如何拆分问题,定义状态 - 如何计算
dp[i]
,即通过之前的状态如何计算下一个状态
除了这两个难点之外,还有一点就是要处理初始值 以及可能的边界溢出问题。
另外,如果题目是要求给出所有解,肯定不用DP。DP 一般是求 最优解或者解法数。
简单的动态规划
最简单的一种动态规划,参见这一题: https://leetcode.com/problems/triangle/
这里我们用一个和题目中给出的 triangle 一样的数组 dp
来存储状态。解决上面说到的两个难点:
- 如果拆分问题?其实这里
dp[i][j]
就表示跳转到triangle[i][j]
这个点所需要的最短路径。 - 如果计算
dp[i][j]
?dp[i][j] 其实就是取他的上层的临近节点中的比较小的一个dp[i][j] = Math.min(last[j-1], last[j])+t[j]
然后再把边界条件处理一下,这个问题就可以解决了。代码如下:
var minimumTotal = function(triangle) {
if(!triangle.length) return 0;
var dp = [triangle[0]];
//注意这里处理的初始值
for(var i=1;i<triangle.length;i++) {
var last = dp[i-1];
var r