322. 零钱兑换 + 动态规划 + 完全背包 + 硬币问题

题目来源

LeetCode_322

题目详情

给你一个整数数组 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

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104

题解分析

解法一:动态规划之完全背包变形

  1. 本题与背包问题极其类似,特别是看到【所有】等关键词,以及题目中提到的【假设每一种面额的硬币有无限个】更可以确信使用完全背包解决。
  2. 我们假设dp[i][j]表示使用前i种硬币凑成金额j需要的最小硬币数,那么什么情况可以推进状态的变化呢?
    • 第一种情况是我们只选前i-1种硬币就能拼凑出j金额。
    • 第二种情况是现在只能拼凑出j-coins[i-1]金额,但是也使用了i种硬币。这种情况比较难以理解,我们可以这样想,因为每种硬币的个数都是无限的,即使我们之前使用了第i种硬币使硬币金额总数达到了j-coins[i-1],我们还是可以重复使用第i种硬币来使结果金额达到j。
  3. 综上,我们可以得出动态转移方程:dp[i][j] = min(dp[i-1][j], dp[i][j - coins[i]] + 1);
  4. 需要注意的是,以上的状态转移方程只有在j>=coins[i]时才适用,当j<coins[i]时,dp[i][j] = dp[i-1][j],即后一种情况不用考虑。
  5. 此外,本题还有一个关键点就是边界值的初始化。这里需要把所有的dp[0][j]赋值为最大值,即表示不使用任何硬币一定无法拼凑出任何金额;把所有dp[i][0]赋值为0表示使用前i种硬币拼凑出金额0只有0种方案,即什么硬币都不使用。
class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        // dp[i][j]表示使用前i种硬币凑成金额j需要的最小硬币数
        int[][] dp = new int[n+1][amount+1];
        // dp[i][j] = min(dp[i-1][j], dp[i][j-coins[i]] + 1);
        int maxs = 0x3f3f3f3f;

        for(int j=0; j<=amount; j++){
            dp[0][j] = maxs;
        }

        for(int i=0; i<=n; i++){
            dp[i][0] = 0;
        }
        for(int i=1; i<=n; i++){
            for(int j=0; j<=amount; j++){
                if(j >= coins[i-1]){
                    dp[i][j] = Math.min(dp[i-1][j], dp[i][j-coins[i-1]] + 1);
                }else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[n][amount] >= maxs ? -1 : dp[n][amount];
    }
}

解法二:动态规划-压缩数组

  1. 类似于其他的动态规划题目,本题可以使用压缩数组来优化存储空间。
  2. 具体地说,就是本题中的第一维可以不需要,而在遍历中的i-1就可以用dp本身来表示,因为当前i状态就是从i-1状态转换而来的。
  3. 与解法一类似,这里也需要注意边界值的转换,除了dp[0]需要赋值为0,其他所有位置均赋值为最大值。
class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        // dp[i][j]表示使用前i种硬币凑成金额j需要的最小硬币数
        int[] dp = new int[amount+1];
        // dp[i][j] = min(dp[i-1][j], dp[i][j-coins[i]] + 1);
        int maxs = 0x3f3f3f3f;
        Arrays.fill(dp, maxs);
        dp[0] = 0;
        for(int i=1; i<=n; i++){
            for(int j=coins[i-1]; j<=amount; j++){
                dp[j] = Math.min(dp[j], dp[j-coins[i-1]] + 1);
            }
        }
        return dp[amount] >= maxs ? -1 : dp[amount];
    }
}

相似题目

LeetCode-279 完全平方数
LeetCode-322. 零钱兑换
LeetCode-518. 零钱兑换 II

posted @ 2021-04-03 21:30  Garrett_Wale  阅读(344)  评论(0编辑  收藏  举报