今天在LeetCode上练了下零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1
ps:可以认为每种硬币的数量是无限的
这个属于完全背包类型问题,我们可以通过动态规划,去解决该问题动态规划的定义我就不重复了. 下面我直接贴出答案
int [] coins = {3, 5};
public int coinChange(int[] coins, int amount) {
int [] dp = new int[amount + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
// dp curr : [0, MAX, MAX...];
for (int currAmount = 1; currAmount <= amount; currAmount++) {
for (int coin : coins) {
int leftAmount = currAmount - coin;
if (leftAmount < 0 || dp[leftAmount] == Integer.MAX_VALUE) continue;
dp[currAmount] = Integer.min(dp[leftAmount] + 1, dp[currAmount]);
}
}
return dp[amount] == Integer.MAX_VALUE ? -1: dp[amount] ;
}
自下而上找出子问题的最优解,先计算出需要总金额之前的最优解;
dp数组存储了子问题的最优解,如图,如果我们要找到11的最优解,那么我们应该从不同价值硬币分别找到最优解
dp[currAmount] = Integer.min(dp[leftAmount] + 1, dp[currAmount]);
- currAmount是当前需要求出需要最少硬币的金额(即子问题)
- +1 是因为 dp[leftAmount]是其他子问题的最优解,应该再加上一个硬币,才是当前要求的答案
- 会循环硬币面额的数组,以此找到不同子问题的最优解,找到需要最少数量硬币的答案
下面是我把执行逻辑进行了推导
对于红色区域无解内容,我们通过下面代码进行规避,也就是剪枝
if (leftAmount < 0 || dp[leftAmount] == Integer.MAX_VALUE) continue;
这样看可能直观感受不到,你可以想象图片反过来(Amount在最下面),这样可能会清晰一点