上一篇文章上提到硬币找零的例子,现在我们实战动态规划就从硬币找零开始
问题描述:
给定 n 种不同面值的硬币,分别记为 c[0], c[1], c[2], … c[n],同时还有一个总金额 k,编写一个函数计算出最少需要几枚硬币凑出这个金额 k?每种硬币的个数不限,且如果没有任何一种硬币组合能组成总金额时,返回 -1。
这里我们先回忆一下动态规划问题的处理过程:
我们处理动态规划问题的时候需要分为这么几步:
1)确定初始化状态,初始化状态作为整个求解链路的原点,需要优先明确;
2)状态参数,中间状态在一步一步推导出最终状态的过程中会发生变化的变量;
3)明确决策方式,即:如何通过前面的状态推导出后面的状态;
4)中间状态存储,子问题存在大量重复计算的情况,我们将中间状态存储入“备忘录”。
确定初始化状态和状态参数
原文中明确指定出了硬币的面值(多个固定值),没有限制硬币的总个数(这是我们需要求解的结果),因而这两个无法作为我们的状态参数,那么状态参数只剩下的总金额k了。
显而易见,我们可以得出结论初始化状态是总金额为0的状态
决策方式
原文要求的是使用硬币凑出金额k,那么在我们每挑出一个硬币的时候就会改变状态,这就是这道题目的决策方式
中间状态的存储
当我们需要求解k=11的时候,k=11-c[n] 会被计算,以此类推,因而我们需要有一个中间状态的决策表存储子问题的解.
状态转移方程
现在我们可以尝试得出状态转移方程
-1 (k<0) 异常情况也考虑下
f(k)= 0 (k=0)
min(1+f(k-c[i])) 0<i<n
到这里,感觉我们几乎可以直接写出代码了,别急,如果只是盲目的根据状态转移方程编写程序,那程序中依然存在着许多重复计算的子问题
可能从第一眼我们看这就是一个递归逻辑,子问题的答案是在不断向上返回,为什么还存在子问题的重复计算,其实我们可以展开来思考下这个程序,画个图方便理解重复计算出现在哪里
假定硬币面值是1,3,5 ,目标总金额是11

如图所示:由于递归是自顶向下的一个求解过程,F(7),F(5)等等在不同的分支中都会被重复计算,有一个解决方法就是将子问题缓存到中间表中,这样我们每遇到一个子问题,就到中间表中去尝试获取已知解,代码如下所示:
public int collect(int[] coins, int target) {
Map<Integer, Integer> cache = Maps.newHashMap();
int result = recursionCollect


118

被折叠的 条评论
为什么被折叠?



