题目:零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 0 。你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
首先我先阐述一下我对于动态规划的一些理解:
1.动态规划是通过运用结果集中产生的最优结果去推出下一需要的结果,即用0 ~ (n - 1)结果间结果的规律推出第n个结果。
2.动态规划通过计算最优解的值,通常自底向上或者自顶向下
3.动态规划就是将大问题分成小问题求解,将一个个小问题的解组合求得大问题的解。
我的理解暂时就是这么多,毕竟也是萌新。
题目分析:刚看问题感觉通过贪心算法也能求出来,但是如果coins = [5,3], amount = 9 的话会发现求不出来。这题用动态分析求的话我们要明白要求的结果集是什么,此题目的结果集是求0 ~amount 下可用的当前可用最少硬币(coins ),得result[i][j],i代表当前0 ~ i的可用硬币,j代表当前所求金额。我们通过表格寻找result[i][j]结果与result[0 ~ i][0 ~ j]间的关系,如下图:
分析:
1)对于coins硬币数组中有的是用与不用的区别
2)对于j >= 3的结果集中,我们发现amount (3)= amount (1)+ amount (2);amount (7)= amount (6)+ amount (1),amount (7)= amount (5)+ amount (2),amount (7)=amount (3)+ amount (4)…等这样的规律。为什么不说amount (9)= amount (3)*3呢,因为结果是和amount (6)+ amount (3)是一样的所以就不考虑了。
所以我们得出 result[i][j]结果与result[0 ~ i][0 ~ j]间的关系:
当 amount == coins[i]时,就取1;
当 amount != coins[i]时,就取组合结果中最小的数(参考分析结论2中的组合结果,注意考虑0,关于怎么求最小结果我就直接用代码展示了);
测试如下:
代码如下:
package 算法.动态规划;
import java.util.Scanner;
/*
* 零钱兑换
*
* */
public class AskingChange {
int amount;
int[] coins;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int amount = scanner.nextInt();
AskingChange askingChange = new AskingChange(amount);
int coinsNumbers = scanner.nextInt();
// 下标从1开始
int[] coins = new int[coinsNumbers + 1];
for (int i = 1; i < coins.length; i++) {
coins[i] = scanner.nextInt();
}
askingChange.setCoins(coins);
// 结果记录
int[][] result = new int[coins.length][amount+1];
askingChange.getMinCoins(coins.length-1, amount, result);
System.out.println(result[coins.length-1][amount]);
scanner.close();
}
public void getMinCoins(int coinsIndex,int amountLength,int[][] result){
// 外层的for表示开放的coins可用数
for (int i = 1; i <= coinsIndex; i++) {
for (int j = 1; j <= amountLength; j++) {
for (int k = 1; k <= i; k++) {
// 刚好等于面额
if (j == coins[k]) {
result[i][j] = 1;
break;
}
if (j != coins[k]) {
result[i][j] = getMin(result[i],j);
}
}
}
}
}
// 获取0到i中组合方法的最小硬币数
public int getMin(int[] result,int resultLength) {
int min ;
if (resultLength <= 1){
return 0;
}else if (resultLength ==2) {
return result[1] * 2;
}
min = result[1]+result[resultLength-1];
// 如果没结果
if ((result[1] == 0) | (result[resultLength-1] == 0)){
// 赋予int最大值的
min = 2147483647;
}
for (int i = 1; i <= resultLength / 2; i++) {
// 跳过无结果的数
if ((result[i] == 0) | (result[resultLength - i] == 0)) {
continue;
}
if (min > (result[i] + result[resultLength - i])) {
min = result[i] + result[resultLength - i];
}
}
if (min == 2147483647){
min = 0;
}
return min;
}
public AskingChange(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public int[] getCoins() {
return coins;
}
public void setCoins(int[] coins) {
this.coins = coins;
}
}
后来我发现其实用一维数组记录结果集也可以,就是不限制硬币必须从0~ i依次解开封印,而是可以直接用coins内所有的硬币,但是懒癌发作就懒得改了。写下博客记录一下学习过程,如果其中有什么说得不对的,望各位大佬指出。