前言
本文思想和借鉴代码皆源自《labuladong的算法小抄一书》,博客所连载的算法小抄系列为该书的读书笔记。原书中用Python实现了凑零钱问题,现用Java实现,以检验学习成果。
普通递归算法
package com.company;
import static java.lang.System.out;
import java.util.*;
public class Main {
// amount为要凑零钱的金额,coins为硬币的种类,dp返回凑出目标金额amount最少所需的硬币数量
public static int dp(int amount, ArrayList<Integer> coins){
if (amount == 0) return 0; // 金额为0,要求的硬币数量为0
if (amount < 0) return -1; // -1表示问题没有解,即该硬币凑不出零钱
int res = Integer.MAX_VALUE;
for (int coin : coins) {
int subProblem = dp(amount - coin, coins); // 减去当前硬币面值,得到凑出剩余金额所需最少的硬币数量
if (subProblem == -1) continue; // 若subProblem不为1,则当前面值可以凑硬币,反之亦然
res = Integer.min(res, 1 + subProblem); // 与上以结果比较,去最小值(注意:加1是加上当前这个面值硬币)
}
if (res != Integer.MAX_VALUE)
return res;
else
return -1;
}
public static int coinChange(int amount, ArrayList<Integer> coins){
return dp(amount, coins);
}
public static void main(String[] args) {
ArrayList<Integer> coins = new ArrayList<>(Arrays.asList(1, 2, 5));
int amount = 11;
out.println(coinChange(amount, coins));
}
}
备忘录优化
稍加修改,即可通过备忘录(字典)记录那些重复计算的结点。
package com.company;
import static java.lang.System.out;
import java.util.*;
public class Main {
// amount为要凑零钱的金额,coins为硬币的种类,dp返回凑出目标金额amount最少所需的硬币数量
public static int dp(int amount, ArrayList<Integer> coins, HashMap<Integer, Integer> memo){
if (memo.containsKey(amount))
return memo.get(amount);
if (amount == 0) return 0; // 金额为0,要求的硬币数量为0
if (amount < 0) return -1; // -1表示问题没有解,即该硬币凑不出零钱
int res = Integer.MAX_VALUE;
for (int coin : coins) {
int subProblem = dp(amount - coin, coins, memo); // 减去当前硬币面值,得到凑出剩余金额所需最少的硬币数量
if (subProblem == -1) continue; // 若subProblem不为1,则当前面值可以凑硬币,反之亦然
res = Integer.min(res, 1 + subProblem); // 与上以结果比较,去最小值(注意:加1是加上当前这个面值硬币)*/
}
if (res == Integer.MAX_VALUE)
memo.put(amount, -1);
else
memo.put(amount, res);
return memo.get(amount);
}
public static int coinChange(int amount, ArrayList<Integer> coins){
HashMap<Integer, Integer> memo = new HashMap<>();
return dp(amount, coins, memo);
}
public static void main(String[] args) {
ArrayList<Integer> coins = new ArrayList<>(Arrays.asList(1, 2, 5));
int amount = 11;
out.println(coinChange(amount, coins));
}
}
dp数组优化
接下来放弃递归的思路,采用循环+数组的方式解决问题。
package com.company;
import static java.lang.System.out;
import java.util.*;
public class Main {
public static int coinChange(int amount, ArrayList<Integer> coins){
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1); // 将数组所有值初始化为amount + 1,这样方便接下来比较大小
dp[0] = 0;
for (int i = 0; i < dp.length; i++){
for (int coin: coins){
if (i - coin < 0) continue;
dp[i] = Integer.min(1 + dp[i - coin], dp[i]);
}
}
return (dp[amount] == amount + 1) ? -1 : dp[amount];
}
public static void main(String[] args) {
ArrayList<Integer> coins = new ArrayList<>(Arrays.asList(1, 2, 5));
int amount = 11;
out.println(coinChange(amount, coins));
}
}