518. 零钱兑换 II

518. 零钱兑换 II

题目链接:518. 零钱兑换 II(中等)

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

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

提示:

  • 1 <= coins.length <= 300

  • 1 <= coins[i] <= 5000

  • coins 中的所有值 互不相同

  • 0 <= amount <= 5000

解题思路

题目中说“假设每一种面额的硬币有无限个。 ”,通过这一句话可以判断这是一道完全背包问题。

但本题要求的是凑成总金额数有几种方法,而不是求能得到的最大金额数。

而且本题不强调元素之间的顺序,属于组合问题排列问题是强调元素之间的顺序的。

运用动态规划五部曲解决问题:

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

    dp[j]表示凑成金额数为j的货币组合有多少种

  2. 确定递推公式

    不考虑coins[i]的情况下,凑成金额数为j - coins[i]的货币组合方法有dp[j - coins[i]]种。

    也就是当前凑成金额数为j的方法数 = 之前凑成金额数为j的方法数 + 之前凑成金额数为j - nums[i]的方法数。于是得到递推公式为:dp[j] = dp[j] + dp[j - coins[i]]

  3. dp数组的初始化

    用0个货币凑成金额数为0的方法有一种。

    所以dp[0] = 1

    下标非0的dp[j]初始化为0,这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j]

  4. 确定遍历顺序

    纯完全背包问题中,先遍历物品还是先遍历背包都可以。但本题是组合问题,并不关心硬币使用的顺序,而是硬币有没有被用到,所以本题必须先遍历物品。如果是排列问题,即有顺序地进行排列,那就需要先遍历背包了 。

    总结:

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

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

  5. 举例推导dp数组(略)

C++

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount + 1, 0);
        dp[0] = 1;
        for (int i = 0; i < coins.size(); i++) {
            for (int j = coins[i]; j < amount + 1; j++) {
                dp[j] = dp[j] + dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
};

JavaScript

/**
 * @param {number} amount
 * @param {number[]} coins
 * @return {number}
 */
var change = function(amount, coins) {
    const dp = Array(amount + 1).fill(0);
    dp[0] = 1;
    for (let i = 0; i < coins.length; i++) {
        for (let j = coins[i]; j < amount + 1; j++) {
            dp[j] = dp[j] + dp[j - coins[i]];
        }
    }
    return dp[amount];
};

 

posted @ 2022-03-05 20:29  wltree  阅读(29)  评论(0编辑  收藏  举报