夯实算法-9.零钱兑换

270 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情

题目:LeetCode

给你一个整数数组 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] <= 2^31 - 1
  • 0 <= amount <= 10000

解题思路

根据题中描述是要求最少硬币数,那很显然是动态规划算法题型。那按照动态规划步骤分析:

  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]);

  1. dp数组初始化值
    凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;
    其他下标对应的数值如何获取到呢? 递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。
    所以下标非0的元素都是应该是最大值。

  2. 确定遍历顺序
    本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数
    所以并不强调集合是组合还是排列。

  3. 根据递推公式将动态数组剩余元素计算出来, dp[amount]即为最终结果

代码实现

public int coinChange(int[] coins, int amount) {
    int max = Integer.MAX_VALUE;
    int[] dp = new int[amount + 1];
    //初始化dp数组为最大值
    for (int j = 0; j < dp.length; j++) {
        dp[j] = max;
    }
    //当金额为0时需要的硬币数目为0
    dp[0] = 0;
    for (int i = 0; i < coins.length; i++) {
        //正序遍历:每个硬币可以选择多次
        for (int j = coins[i]; j <= amount; j++) {
            //只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要
            if (dp[j - coins[i]] != max) {
                //选择硬币数目最小的情况
                dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
            }
        }
    }
    return dp[amount] == max ? -1 : dp[amount];
}

获取数组初始值dp[0] = 0;通过遍历和比较获取到剩余的元素值,最终获取到最终结果。

运行结果

9.png

复杂度分析

  • 时间复杂度:O(n2)O(n^2)
  • 空间复杂度: O(n)O(n)

掘金(JUEJIN)  一起分享知识, Keep Learning!