322. 零钱兑换

阳光穿透心脏的1/2处 2023-10-16 17:04 98阅读 0赞

322. 零钱兑换

  • 题干分析
  • 解题思路
      1. 确定dp数组以及下标的含义
      1. 确定递推公式
      1. dp数组如何初始化
      1. 确定遍历顺序
      1. 举例推导dp数组
  • 总结
  • 代码实现

题干分析

力扣入口

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

你可以认为每种硬币的数量是无限的。

示例 1:

  • 输入:coins = [1, 2, 5], amount = 11
  • 输出:3
  • 解释:11 = 5 + 5 + 1

示例 2:

  • 输入:coins = [2], amount = 3
  • 输出:-1

示例 3:

  • 输入:coins = [1], amount = 0
  • 输出:0

示例 4:

  • 输入:coins = [1], amount = 1
  • 输出:1

示例 5:

  • 输入:coins = [1], amount = 2
  • 输出:2

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 2^31 - 1
  • 0 <= amount <= 10^4

解题思路

动态规划:518.零钱兑换II中我们已经兑换一次零钱了,这次又要兑换,套路不一样!

题目中说每种硬币的数量是无限的,可以看出是典型的完全背包问题。

动规五部曲分析如下:

1. 确定dp数组以及下标的含义

dp[j]:凑足总额为j所需钱币的最少个数为dp[j]

2. 确定递推公式

凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j](考虑coins[i])

所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。

递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);

3. dp数组如何初始化

首先凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;

其他下标对应的数值呢?

考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。

所以下标非0的元素都是应该是最大值。

4. 确定遍历顺序

本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数

所以本题并不强调集合是组合还是排列。

如果求组合数就是外层for循环遍历物品,内层for遍历背包。

如果求排列数就是外层for遍历背包,内层for循环遍历物品。

在动态规划专题我们讲过了求组合数是动态规划:518.零钱兑换II,求排列数是动态规划:377. 组合总和 Ⅳ

所以本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包或者外层for遍历背包,内层for循环遍历物品都是可以的!

那么这里采用coins放在外循环,target在内循环的方式。

本题钱币数量可以无限使用,那么是完全背包。所以遍历的内循环是正序。

综上所述,遍历顺序为:coins(物品)放在外循环,target(背包)在内循环。且内循环正序。

5. 举例推导dp数组

以输入:coins = [1, 2, 5], amount = 5为例
在这里插入图片描述
dp[amount]为最终结果。

总结

细心的同学看网上的题解,可能看一篇是遍历背包的for循环放外面,看一篇又是遍历背包的for循环放里面,看多了都看晕了,到底两个for循环应该是什么先后关系。

而本题是要求最少硬币数量,硬币是组合数还是排列数都无所谓!所以两个for循环先后顺序怎样都可以!

代码实现

  1. class Solution {
  2. // 322. 零钱兑换
  3. public int coinChange(int[] coins, int amount){
  4. int max = Integer.MAX_VALUE;
  5. int[] dp = new int[amount + 1];
  6. // 初始化dp数组为最大值
  7. for(int j = 0; j < dp.length; j++){
  8. dp[j] = max;
  9. }
  10. //当金额为0时需要的硬币数目为0
  11. dp[0] = 0;
  12. for (int i = 0; i < coins.length; i++){
  13. //正序遍历: 完全背包每个硬币可以选择多次
  14. for(int j = coins[i]; j <= amount; j++){
  15. //只有dp[j - coins[i]]不是初始最大值时,该位才有选择的必要
  16. if(dp[j - coins[i]] != max){
  17. //选择硬币数目最小的情况
  18. dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
  19. }
  20. }
  21. }
  22. return dp[amount] == max ? -1 : dp[amount];
  23. }
  24. }

参考资料:代码随想录-322. 零钱兑换

发表评论

表情:
评论列表 (有 0 条评论,98人围观)

还没有评论,来说两句吧...

相关阅读

    相关 322. 零钱兑换

    > 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 >

    相关 Leetcode 322. 零钱兑换

    题目重述 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何

    相关 LeetCode-322. 零钱兑换

    [322. 零钱兑换][322.] 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币

    相关 322.零钱兑换

    //给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 // -1