动态规划算法通常基于一个递推公式以及一个或多个初始状态,当前子问题的解由上一次子问题的解推出。
在动态规划算法中有一个经典的例子就是硬币找零问题。
1、问题描述
如果我们有面值为1元、3元、5元的硬币若干,如何用最少的硬币凑够11元?
2、思路分析
基于动态规划的思想,我们可以从1元开始计算最少需要几个硬币,然后再求2元、3元、4元...
首先,当i=0时,我们需要0个,即d(0)=0;
当i=1时,只有面值为1元的硬币可用,d(1)=d(1-1)+1=1;
当i=2时,仍然只有面值为1元的硬币可用,d(2)=d(2-1)+1=2;
当i=3时,可用的硬币有1元和3元,如果我拿了1元的,我的目标就变成了凑够3-1=2元需要的最少硬币了,即d(3)=d(3-1)+1=3,另一个方案是我拿了一个3元的硬币,目标就是凑够3-3=0元需要的最少硬币数量,即d(3)=d(3-3)+1=1,所以综合两者方案的最小值d(3)=min{d(3-1)+1,d(3-3)+1}=1;
当i=4时,可用的硬币有1元和3元,如果我拿了1元的,我的目标就变成了凑够4-1=3元需要的最少硬币了,即d(4)=d(4-1)+1=2,另一个方案是我拿了一个3元的硬币,目标就是凑够4-3=1元需要的最少硬币数量,即d(4)=d(4-3)+1=2,所以综合两者方案的最小值d(3)=min{d(4-1)+1,d(4-3)+1}=2;
d(5)=1;
d(6)=2;
d(7)=3;
d(8)=2;
d(9)=3;
d(10)=2;
...
由上面可以退出公式,d(i)=min{d(i-vj)+1},其中i-vj>=0,vj表示第j个硬币的面值。
现在,我们回到原题中,d(11)=min{d(11-vj)+1},其中vj可以取值为1,3,5。当vj=1时,d(11)=d(10)+1=3,当vj=3时,d(11)=d(8)+1=3,当vj=5时,d(11)=d(6)+1=3,所示d(11)=3。
3、代码示例
首先定义以下变量:
values[] : 保存每一种硬币的币值的数组
valueKinds :币值不同的硬币种类数量,即values[]数组的大小
money : 需要找零的面值
coinsUsed[] : 保存面值为 i 的纸币找零所需的最小硬币数
当求解总面值为 i 的找零最少硬币数 coinsUsed[ i ] 时,将其分解成求解 coinsUsed[ i – cents]和一个面值为 cents 元的硬币,由于 i – cents < i , 其解 coinsUsed[ i – cents] 已经存在,如果面值为 cents 的硬币满足题意,那么最终解 coinsUsed[ i ] 则等于 coinsUsed[ i – cents] 再加上 1(即面值为 cents)的这一个硬币。代码如下:
public class CoinsChange { /** * 硬币找零:动态规划算法 * */ public static void makeChange(int[] values, int valueKinds, int money, int[] coinsUsed) { coinsUsed[0] = 0; int cents ; // 对每一块钱都找零,即保存子问题的解以备用,即填表 for (cents = 1; cents <= money; cents++) { // 当用最小币值的硬币找零时,所需硬币数量最多 int minCoins = cents; // 遍历每一种面值的硬币,看是否可作为找零的其中之一 for (int kind = 0; kind < valueKinds; kind++) { // 若当前面值的硬币小于当前的cents则分解问题并查表 if (values[kind] <= cents) { int temp = coinsUsed[cents - values[kind]] + 1; // temp表示组合成cents需要的硬币数目 if (temp < minCoins) { minCoins = temp; } } } // 保存最小硬币数 coinsUsed[cents] = minCoins; } System.out.println("面值为 " + (money) + " 的最小硬币数 : " + coinsUsed[cents-1]); } public static void main(String[] args) { int[] coinValue = new int[] { 1,3,5 }; // 需要找零的面值 int money = 11; // 保存每一个面值找零所需的最小硬币数,0号单元舍弃不用,所以要多加1 int[] coinsUsed = new int[money + 1]; makeChange(coinValue, coinValue.length, money, coinsUsed); } }