转眼又到了金三银四的时候,是不是又有不少小伙伴带着,对当前公司的不满,带着对新工作的渴望,开启了新一轮的面试?提到面试,估计不少人的经历都是相似的吧,八股文背诵的滚瓜烂熟,但是一遇到算法题就开始抓耳挠腮,不知所措。这其中,动态规划最为折磨人!!!
那么从今天开始,我们一起学习动态规划。
什么是动态规划
动态规划是运筹学的一个分支,是求解决策过程最优化的过程
动态规划的基本思想
动态规划主体思想是将待求解的问题分解为若干个子问题(这一点与分治法相似),按顺序求解子问题,前面子问题的解,对后面子问题的求解有帮助(这一点与分治法不同,分治法是各个子问题相对独立)。
动态规划有什么特征
动态规划问题有以下三个主要特征:
无后效性
最优子结构
重叠子问题
重叠子问题
子问题之间互相不独立,每一个子问题的解,可能被后面其他子问题在求解的过程中被用到
这里举一个简单的例子:
斐波那契数列
很多人在求解斐波那契数列的时候编写的程序如下:
public int fibonacci(int n) {
if(n == 0 || n == 1) {
return n;
}
return fibonacci(n-1) + fibonacci(n-2);
}
这里n-3的值在f(n-1)和f(n-2)的里面都被重复计算了,在遇到比较大的问题求解时候,这就会造成较大的资源浪费
最优子结构
子问题之间必须互相独立,后面的状态可以通过前面的状态推导计算出来
这里举一个硬币找零的例子:
有5块和3块两种面额的硬币若干,凑出target=11块的最少硬币数
其中一个子问题target=6的解是2(2个3块),那么我们可以得出target=11的时候,解为3(增加一个5块)
原问题没有限制硬币的个数(若干个可以随意取),因而,我们在求解target=11的时候可以直接从target=6得出结论
这就是子问题之间互相独立,没有互相制约的情况。
无后效性
子问题之间的依赖是单向的,某个子问题的结果一旦确定,那么后面的子问题对它将不会产生任何影响
依然可以以上面的硬币问题为例,target=6的子问题确定了结果为2以后,后续target=7,8,9问题如何抉择都不会影响到它,这就是单向依赖,后面的问题依赖于前面的子问题求解,但是不会影响前面的结果。
如何处理动态规划问题
按照上面提到的动态规划的特性,可以得出这样一个结论,动态规划的问题的处理过程就是一个接一个处理子问题的过程,子问题直接存在关联,关联的表现形式就是——决策,换个说法就是根据前面子问题的解,我们可以通过决策得到当前子问题的解。
从这个角度看,子问题也可以看作一个个状态,问题的最终解就是最终状态,子问题就是中间状态,我们需要从最初始的状态开始经过中间状态一步一步决策得到最终状态。
如图所示(每一个→表示一次决策)
初始状态 → 中间状态 1 → 中间状态2→ … → 中间状态n→ 最终状态
基于此,我们处理动态规划问题的时候需要分为这么几步:
- 确定初始化状态,初始化状态作为整个求解链路的原点,需要优先明确;
- 状态参数,中间状态在一步一步推导出最终状态的过程中会发生变化的变量;
- 明确决策方式,即:如何通过前面的状态推导出后面的状态;
- 中间状态存储,子问题存在大量重复计算的情况,我们将中间状态存储入“备忘录”。
当以上几个步骤都完成后,我们可以写出状态与状态参数之间的关系——状态转移方程
说到这里,不得不明确一下动态规划的主要难点就是明确出各个阶段的状态,并找到各个状态之间的推导(决策)关系,所以当我们能写出状态转移方程的时候,问题就已经解决了90%
算法实现
前面提到动态规划是由子问题到原问题的一步一步推导转化而解决,从这个角度来说,动态规划往往可以用递归程序来实现。
在我们已经确定了上面的初始化状态等等问题了以后,我们就可以通过一个最优决策表来描述我们的求解过程,填表的过程就相当于递推的过程。
关于动态规划的基础知识我们就了解到这里,之后我们可以基于这些基础知识,开始动态规划问题的实战。请期待后面的推文!!!
如果觉得本文有帮助可以分享给自己的小伙伴们!
欢迎关注我的微信公众号
微信搜索
小哥爱code