arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim。
每个值都认为是一种面值,且认为张数是无限的。
返回组成aim的最少货币数
暴力解法
public static int minCoins(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim <= 0) {
return 0;
}
return process(arr, aim, 0);
}
//从左往右,进行选择,返回正好组成aim的最少货币数。
//用 Integer.MAX_VALUE表示搞不定。
public static int process(int[] arr, int aim, int index) {
if (index == arr.length) {
return aim == 0 ? 0 : Integer.MAX_VALUE;
}
//coins是收集可以搞定的情况下,最少货币张数。
int coins = Integer.MAX_VALUE;
//当前位置的货币拿几张。
for (int zhang = 0; aim - zhang * arr[index] >= 0; zhang++) {
int p = process(arr, aim - zhang * arr[index], index + 1);
//只有在可以搞定的情况下才可以计算最少货币的张数。
if (p != Integer.MAX_VALUE) {
coins = Math.min(coins, zhang + p);
}
}
return coins;
}
也可以是如下
public static int minCoins(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim <= 0) {
return 0;
}
return process(arr, aim, 0);
}
public static int process2(int[] arr, int aim, int index) {
if (index == arr.length) {
return aim == 0 ? 0 : -1;
}
int ans = Integer.MAX_VALUE;
for (int zhang = 0; aim - zhang * arr[index] >= 0; zhang++) {
int p = process2(arr, aim - zhang * arr[index], index + 1);
//当for循环的条件被打破时,向上层返回的将是Integer.MAX_VALUE,上层P接收到后,如果没有如下条件限制直接进 //行zhang + p就会越界。
//解释一下如何打破for循环 ->当最后一层的张数不断增加但是一直凑不齐整后,就会发生 //aim - zhang * arr[index] < 0
if (p != -1 && p != Integer.MAX_VALUE) { //关键点
ans = Math.min(zhang + p, ans);
}
}
return ans;
}
public static int dp1(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim <= 0) {
return 0;
}
int N = arr.length;
int[][] dp = new int[N + 1][aim + 1];
dp[N][0] = 0;
for (int index = 1; index <= aim; index++) {
dp[N][index] = Integer.MAX_VALUE;
}
for (int index = N - 1; index >= 0; index--) {
for (int aimIndex = 0; aimIndex <= aim; aimIndex++) {
int coins = Integer.MAX_VALUE;
//当前位置的货币拿几张。
for (int zhang = 0; aimIndex - zhang * arr[index] >= 0; zhang++) {
int p = dp[index + 1][aimIndex - zhang * arr[index]];
//只有在可以搞定的情况下才可以计算最少货币的张数。
if (p != Integer.MAX_VALUE) {
coins = Math.min(coins, zhang + p);
}
}
dp[index][aimIndex] = coins;
}
}
return dp[0][aim];
}
DP方法
public static int dp1(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim <= 0) {
return 0;
}
int N = arr.length;
int[][] dp = new int[N + 1][aim + 1];
dp[N][0] = 0;
for (int index = 1; index <= aim; index++) {
dp[N][index] = Integer.MAX_VALUE;
}
for (int index = N - 1; index >= 0; index--) {
for (int aimIndex = 0; aimIndex <= aim; aimIndex++) {
int coins = Integer.MAX_VALUE;
//当前位置的货币拿几张。
for (int zhang = 0; aimIndex - zhang * arr[index] >= 0; zhang++) {
int p = dp[index + 1][aimIndex - zhang * arr[index]];
//只有在可以搞定的情况下才可以计算最少货币的张数。
if (p != Integer.MAX_VALUE) {
coins = Math.min(coins, zhang + p);
}
}
dp[index][aimIndex] = coins;
}
}
return dp[0][aim];
}
dp方法进一步优化(斜率优化)
public static int dp2(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim <= 0) {
return 0;
}
int N = arr.length;
int[][] dp = new int[N + 1][aim + 1];
dp[N][0] = 0;
for (int index = 1; index <= aim; index++) {
dp[N][index] = Integer.MAX_VALUE;
}
for (int index = N - 1; index >= 0; index--) {
for (int aimIndex = 0; aimIndex <= aim; aimIndex++) {
dp[index][aimIndex] = dp[index + 1][aimIndex];
//暴力递归中已经明确写好了,要计算货币张数时必须保证格子是yo
//现有这个格子,并且这个格子的值是有效的
if (aimIndex - arr[index] >= 0 && dp[index][aimIndex - arr[index]] != Integer.MAX_VALUE) {
//为什么 dp[index][aimIndex - arr[index]] + 1 后面写着。
dp[index][aimIndex] = Math.min(dp[index][aimIndex - arr[index]] + 1, dp[index][aimIndex]);
}
}
}
return dp[0][aim];
}
注意:
注意这是dp方法中的核心方法。
for (int index = N - 1; index >= 0; index--) {
for (int aimIndex = 0; aimIndex <= aim; aimIndex++) {
int coins = Integer.MAX_VALUE;
//当前位置的货币拿几张。
for (int zhang = 0; aimIndex - zhang * arr[index] >= 0; zhang++) {
int p = dp[index + 1][aimIndex - zhang * arr[index]];
//只有在可以搞定的情况下才可以计算最少货币的张数。
if (p != Integer.MAX_VALUE) {
coins = Math.min(coins, zhang + p);
}
}
dp[index][aimIndex] = coins;
}
}
会发现待填格子p在填写之前都会求其依赖的格子+zhang数的最小值,所以画图的时候一定要对着代码分析全面了。