动态规划(DP)
看B站中动态规划视频学习总结,链接:
【https://www.bilibili.com/video/BV1j7411u7en】
动态规划一般用于求解优化问题,例如最大、最小、最重、最长等
动态规划一般由三个部分组成:
DP=递归+记忆+猜测
我们以斐波拉契数列为例,讲解动态规划为何分这三部分。
f(x)={xx=0,1f(x−1)+f(x−2)其他
f(x)=
\begin{cases}
x& \text{x=0,1}\\
f(x-1)+f(x-2)& \text{其他}
\end{cases}
f(x)={xf(x−1)+f(x−2)x=0,1其他
递归实现树形图,以f(5)为例
从递归树形图中我们可以看出f(3)、f(2)进行了多次求解,若我们在第一次求解f(3)、f(2)后将其存储到列表中,当再次使用时直接进行查表,减少了多余计算量。
f(0) | 0 |
---|---|
f(1) | 1 |
f(2) | 1 |
f(3) | 2 |
f(4) | 3 |
f(5) | 5 |
当有了记录表,我们得递归树形图可表示为
这便是动态规划中得递归,对于猜测我们在实例中讲解
在动态规划过程中,我们就是将一个大问题【例如f(5)】转换成一个个小问题,并在求解小问题记录可再次利用的中间值【例如f(2),f(3)】
动态规划问题求解基本步骤
- 定义子问题
x∈X x \in X x∈X
子问题x是原问题X的一部分(原问题一般指输入数据的规模)
输入数据:一般按序列处理,在求解过程中一般使用前缀法、中缀法、后缀法 - 定义子问题
x∈X x \in X x∈X
子问题x是原问题X的一部分(原问题一般指输入数据的规模)
输入数据:一般按序列处理,在求解过程中一般使用前缀法、中缀法、后缀法
- 建立子问题关系(递归)
- 猜测(不妨设,启动求解过程)
- 子问题的解<—>递归树形图中的节点
子问题求解过程就是节点的遍历(其中节点需要满足拓扑排序:当前节点只与已求解的节点有关)
- 明确边界条件
- 得到原问题的解(自低向上实现)
- 表上遍历
- 分析时间复杂度
O(n)=子问题的个数 x 每一个子问题的求解时间
例题:拾硬币
Input: c[0] c[1] c[2] … c[n-1] (硬币列表)
[5 1 2 10 6 2]
Output:拾三枚硬币使得硬币累加和最大,其中相邻不能捡
优化问题:“最大”
Algo1:
- 生成所有子序列
- 去除不满足条件的序列
- 所有剩余序列累加和最大
- 时间复杂度O(2n)O({2^n})O(2n)
DP(步骤和动态规划问题求解基本步骤相对应):
-
定义子问题
原问题:求解拾三枚硬币使得硬币累加和最大(相邻不能捡)
子问题:前i个硬币c[0:i]中拾硬币使得硬币累加和最大的解记作DP[i](相邻不能捡)
-
建立子问题关系(递归)
猜测:子问题之间的关系
DP(i)=max{DP[i−2]+c[i]第i个硬币包含拾捡硬币中DP[i−1]第i个硬币不包含拾捡硬币中 DP(i)=max \begin{cases} DP[i-2]+c[i]& \text{第i个硬币包含拾捡硬币中}\\ DP[i-1]& \text{第i个硬币不包含拾捡硬币中} \end{cases} DP(i)=max{DP[i−2]+c[i]DP[i−1]第i个硬币包含拾捡硬币中第i个硬币不包含拾捡硬币中
-
明确边界条件
DP[0]=c[0] (只有一枚硬币时,拾这一枚硬币为最大) -
得到原问题的解(自低向上实现)
i | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
c[i] | 5 | 1 | 2 | 10 | 6 | 2 |
DP[i] | 5 | 5 | 7 | 15 | 15 | 17 |
- 时间复杂度O(n)