硬币兑换
本实验选择求解“硬币兑换”问题,要求:计算最优值、构造最优解
硬币兑换:
已知:面额数组 num[0] num[1] num[2]… num[m]
输入:兑换金额=n
最优值函数:dp(n):金额=n 时,最少的硬币兑换数量
输出:最少兑换硬币数量,以及这种兑换方式的解空间
实验目标:
掌握动态规划策略,提升分析解决复杂问题能力
实验要求:
理解动态规划基本原理,掌握动态规划算法设计步骤和程序实现,对算法性能进行分析得出结论
实验内容:
针对动态规划策略的典型应用问题(如0-1背包、矩阵连乘积、最优二叉查找树、硬币兑换等),设计动态规划求解算法,编程实现并分析算法性能
先看代码
package June21;
import java.util.Arrays;
import java.util.Scanner;
public class w0620_study {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("请输入硬币个数:");
int coinnum = in.nextInt();
int num[] = new int[coinnum];
System.out.println("请输入硬币面额:");
for (int i = 0; i < coinnum; i++) {
num[i] = in.nextInt();
}
System.out.println("请输入需要兑换的金额:");
int money = in.nextInt();
System.out.println("需要最少硬币数量为:" + coinChange(num, money));
}
public static int coinChange(int num[], int k) {
int dp[] = new int[k + 1];
int number[] = new int[num.length];
dp[0] = 0;
//dp[1] dp[2] dp[3] dp[4]...dp[k]
for (int i = 1; i <= k; i++) {
dp[i] = Integer.MAX_VALUE;
//dp[i] = min {dp[i-num[0]+1...dp[i-num[num.length-1]+1}
for (int j = 0; j < num.length; j++) {
//if判断防止在Integer.MAX_VALUE+1之后出现越界问题
if (i >= num[j] && dp[i - num[j]] != Integer.MAX_VALUE) {
dp[i] = Math.min(dp[i - num[j]] + 1, dp[i]);
}
}
}
if (dp[k] == Integer.MAX_VALUE) {
dp[k] = -1;
}
int flag = k;
//此while循环为了求解出解空间
while (flag > 0) {
int temp = Integer.MAX_VALUE;
for (int j = 0; j < num.length; j++) {
if (flag >= num[j] && dp[flag - num[j]] != -1) {
temp = Math.min(temp, dp[flag - num[j]]);
}
}
for (int j = 0; j < num.length; j++) {
if (temp == dp[flag - num[j]]) {
flag = flag - num[j];
number[j]++;
break;
}
}
}
System.out.println("解空间为:" + Arrays.toString(number));
return dp[k];
}
}
运行结果(截图):
算法原理:
分析最后一步,虽然我们不知道最优策略是什么,但是最优策略肯定是K枚硬币a1, a2…ak,面值加起来是需要兑换的coins,所以一定有一枚最后的硬币: ak,除掉这枚硬币,前面硬币的面值加起来是coins - ak。我们不关心前面的K-1枚硬币是怎么拼出coins - ak的(可能有1种拼法,可能有100种拼法),而且我们现在甚至还不知道ak和K,但是我们确定前面的硬币拼出了coins - ak。因为是最优策略,所以拼出coins - ak的硬币数一定要最少,否则这就不是最优策略了。
所以我们就要求最少用多少枚硬巾可以拼出coins - ak,原问题是最少用多少枚硬币拼出coins,我们将原问题转化成了一个子问题,而且规模更小:coins - ak。为了简化定义,我们设状态dp(X) = 最少用多少枚硬币拼出X。
所以我们可以得到状态转移方程:dp[i] = min{dp[i-num[0]+1…dp[i-num[num.length-1]+1}。
在求解解空间时,是通过最后的结果倒推出硬币的兑换方式,即不断循环求出构成min{dp[i-num[0]+1…dp[i-num[num.length-1]+1}的解,最终得到解空间。
复杂度分析:
设m为硬币个数 n为需要兑换的硬币金额
时间复杂度为O(m*n),同样的空间复杂度也是O(m*n)。
小结:
通过这个实验,我对动态规划的认识更加深刻,动态规划算法通常用于求解具有某种最优性质的问题,在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
在本实验中,我们可以通过动态规划算法求解出兑换不同金额需要硬币的最少数目。首先,我们规定,dp[0]=0。通过状态转移方程,依次向上递推,求解出需要求解问题的所有子问题,最终求得该问题的答案。
在我看来,动态规划在一定意义上就是用空间换时间,从而达到更高的时间效率,因为这种方法把子问题的答案都用数组存储起来。
在求解动态规划问题时,比较重要的一点就是不断找出子问题,列出状态转移方程,这十分重要。对于硬币兑换问题,求解构造硬币最优解,是根据最后的答案,倒推出硬币组合方式,最终得到解空间。
最后,我们需要在本实验中注意到的一点是,我们把dp[i] (i>0时)的初始化置为Integer.MAX_VALUE,在之后的运算中,为了防止dp[i]越界,在运算时,不能直接把Integer.MAX_VALUE+1,需要先作if判断,这样可以使代码更加合理。