前言
动态规划是一种比较难以理解的算法思想,本文结合自己的理解采用通俗易懂的方式来讲解下动态规划,欢迎各位感兴趣的开发者阅读本文。
思路分析
接下来,我们通过一个例子来逐步分析,引出动态规划思想。
假设,你家里三种面值的钞票(1元、5元、11元)无数张,现在需要用这些钞票凑出某个金额出来,我们需要怎样搭配才能用最少的钞票数量凑出这个金额出来?
例如:我们要凑15元
出来。
贪心思想 - 只顾眼前
依据我们的生活经验,肯定是先用面纸较大的钞票,用总金额做减法运算,思路如下:
- 先拿1张11元的钞票,接下来我们要凑的金额就是
4元
(15 - 11
) - 要凑
4元
出来,我们只能用1元钞票来凑,我们需要4张1元纸币(4 - 1 - 1 - 1 - 1
)
15元凑出来了,我们总共用了5张钞票(1张11元的,4张1元的)。这种策略,我们称之为贪心
,我们把要凑的金额设为w
,需要用的钞票面额设为m
,贪心策略会尽快的让w变的更小,每减少一次,我们接下来要面对的局面就是凑出w - m
。
经过我们大脑计算后,这种策略凑出15元所用的钞票数量并不是最少的,我们直接使用3张5元的钞票就可以凑这个金额。
更好的方案 - 动态规划
我们使用贪心思想来解决这个问题时,只考虑了眼前的情况,格局小了,那么我们现在应该如何避免这种情况呢?
如果使用暴力枚举的方法来凑出金额,明显时间复杂度过高,太多种组合方式可以凑出这个金额了,枚举它们的时间是不可承受的。
重叠子问题
接下来,我们来尝试着,找一下这个问题的性质。
- 一开始,如果我们取了面值为11的钞票,那么接下来面临的问题就是凑出金额为4时所需的最少钞票数量
- 一开始,如果我们取了面值为5的钞票,那么接下来面临的问题就是凑出金额为10时所需的最少钞票数量
- 一开始,如果我们取了面值为1的钞票,那么接下来面临的问题就是凑出金额为14时所需的最少钞票数量
经过上述分析,我们会发现这些问题都有一个共同的形式:给定一个金额,凑出这个金额所需的最少钞票数量。
我们将一个大问题拆解成了三个子问题。
接下来,我们再来分析下这三个子问题,我们用f(n)
来表示凑出n所需的最少钞票数量,用cost
来表示凑出w
所需的钞票数量,那么:
- 如果我们取了11,最后用掉的钞票总数就为:
cost = f(4) + 1
- 如果我们取了5,最后用掉的钞票总数就为:
cost = f(10) + 1
- 如果我们取了1,最后用掉的钞票总数就为:
cost = f(14) + 1
观察上述问题后,我们会发现一个共同点:每取一个面值的钞票,都需要计算剩余金额所需的最少钞票数,而它们的计算方法都是相同的。
这三个子问题都需要用同一种方式求解,那么它们就属于重叠子问题。
最优子结构
当我们要凑出15元的金额时,我们需要的钞票总数就为上述三种情况里所需钞票数量最少的那一个。
我们在求f(n)时,又要算出金额为n时所需的最少钞票数,例如f(10)
,我们只能用2种面值的钞票(5元的和1元的)
- 如果用5元来凑的话,我们需要的钞票数就为:f(5) + 1
- 如果用1元来凑的话,我们需要的钞票数就为:f(9) + 1
我们在求f(n)时,一定会从其子问题的解决方案中找出所需硬币数量最少的那个,即:
f(n) = min(f(n - 1), f(n -5 ), f(n - 11)) + 1
大问题的最优解可以由子问题的最优解推出,这个性质就称为最优子结构。
无后效性
通过上面的分析,我们知道了金额为15时,需要求出3个重叠子问题的解,选出最优的那个就是最终问题的解。
那三个子问题又有自己的重叠子问题,我们在求解这些重叠子问题时,只需要知道最终答案,不用去关心他们是如何算出来的,因为他们的计算过程并不会对之后的问题产生影响。
例如:f(4), f(10), f(14) ,我们只需要求出他们的具体值,他们的计算过程对我们之后要求解的问题没有影响。
如果给定某一阶段的状态,这一阶段以后过程的发展,不受这阶段以前各段状态的影响,就称为无后效性,即:未来与过去无关。
剪绳子
有一根长度为n的绳子,把绳子剪成m段(m、n都是整数,n > 1并且m > 1),每段绳子的长度记为k[0], k[1], …, k[m]。
请问k[m] * k[1] * … * k[m]可能的最大乘积是多少?
例如:当绳子长度为8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
思路分析
接下来,我们来分析这个例子,看看能否用动态规划来解决。
根据题意,我们可知下述信息:
- 绳子的长度肯定大于1且每次至少切一刀
- 我们用f(n)来表示长度为n的绳子所有切法的最大乘积
那么,当绳子的长度为2时,我们只有一种切法,从中间切,这条绳子会被切为长度各为1的两小段,如下图所示:
当n=2时,f(n) = 1 * 1 = 1,即:f(2) = 1
我们继续分析n=3的情况,如下图所示,它有2种切法
- 切成长度为1和长度为2的两小段
- 切成长度分别为1、1、1的三小段
从切法2中,我们可以看出它其实就是对长度为2的绳子进行了一次切分,经过前面的分析,我们已经知道了它的所有切法的最大乘积是1,那么他们的乘积就是1 * 1 * 1 = 1。
因此, 我们不对他进行划分,直接取切法1的乘积,即: f(3)