什么是dp?
dp是英文 dynamic programming,动态的编程。
什么是动态规划?
要了解动态规划,首先需要了解动态规划的两个老前辈:
贪心(Greed): 以当前局部最优的选择来逐步建立一个问题解决方案,最终结果不一定是全局最优解,可以认为是动态规划求解最优化问题的一个特例。
分治(Divide-and-conquer):将问题划分为独立的(independent)子问题,递归地求解子问题(有可能会被反复求解),再将它们的解组合起来,求出原问题的解。
动态规划(Dynamic programming):用分治思想将问题划分为一系列独立的子问题,但各子问题的子子问题重复,即含有重叠的(overlapping)子问题, 则把每个子问题只求解一次(记录在表中),通过组合更小的子问题的解来得到更大子问题的解。
经典再现
用1x2的骨牌填满2n的格子,共有几种填法?
Sol:
以f(n)表示填满2n格子的方法数
观察最后一格放置骨牌的情形:
解决方法:
以f(n)表示填满2*n格子的方法数
观察最后一格放置骨牌的情形:
f(n)=f(n-1)+f(n-2)
初始条件:f(1)=1, f(2)=2
问题转化为斐波拉契数列问题
这个问题实现方法最简单的就是递归了:
这是一种非常简便的办法,理解起来也非常简便,但是——它非常的慢。
时间复杂度:O(f(n))= O(2^n)。
原因:重叠子问题。
如果把深搜遍历看成是一棵树的话,不难发现问题:
不只是方框框出来的地方,重复求解的地方数不胜数。
怎么解决,当然是记下来呀!
动态规划的简单优化——拿空间换时间!
将子问题答案记录(Memoization,备忘录)起来,如果递归遇到相同的问题,则直接查表。
时间复杂度:O(n),比起递归算法,是降维打击
DP= Divide and Conquer + Memoization Programming——用表格存储起来,有 “ 以空间换时间”之含义
搜索树如下:
红色的部分为直接查表的部分。
在经过动归的初步了解后,现在:
以状态和DAG的观点,重新看待动态规划
动态规划类比
“子问题” = 状态;
全部子问题 = 状态空间;
递推关系 = 状态转移方程;
子问题 = 点;
递推关系 = 边;
注:图中箭头的意思是要求箭头指到的问题的话,就必须要用到箭头末端的答案。
由图易得,刚才的题还有一种写法:
因为这个实现方式没有递归调用的开销,表的维护开销也更小,而且,对于某些问题,可以通过改变表的访问模式来进一步降低时空代价,所以目测应该是更优。
但是在动归当中,其实有的状态并不需要一直都保存起,有些子问题的答案用完就可以直接丢弃了,例如这道题,只需要保存上一个和上上个问的答案就可以了。
int fib(int n)
{
if (n < 2) return n;
int prev = 1,curr = 1;
for (int i = 1; i < n; i++) {
int sum = prev + curr;
prev = curr;
curr = sum;
}
return curr;
}