518.零钱兑换 II - medium

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 

 

示例 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
 

注意:

你可以假设:

0 <= amount (总金额) <= 5000
1 <= coin (硬币面额) <= 5000
硬币种类不超过 500 种
结果符合 32 位符号整数

 

 

 

思路:

  • [0-1]背包 问题:当前考虑的物品拿或者不拿;
  • [完全]背包 问题:当前考虑的物品拿或者不拿,如果拿,只要背包能装下,就可以一直拿,直到背包装不下为止。
  • 此题:动态规划(完全背包问题),求组合数,设 dp[i][j] 表示使用前 i种硬币组成金额 j 的组合数;
  • dp[i][j] = dp[i-1][j] + dp[i][ j-coins[i] ] ,即,对于当前的硬币 coins[i] 来说,组成金额 j 有两种情况,如果不使用这个硬币 i,而是使用前 i-1个硬币组成金额 j 的组合数为 : dp[i-1][j] ,如果使用这个硬币 i,则组合数等于 : dp[i][j - coins[i] ];

          

 

                         注:图片来源 Liucx

  • 一个一个物品考虑,容量一点一点扩大,整个过程就是尝试和比较的过程。

  •  代码(二维矩阵):

class Solution {
    public int change(int amount, int[] coins) {
        int n = coins.length;
        int[][] dp = new int[n + 1][amount + 1];
        dp[0][0] = 1; //当 amount = 0 时,有 1 种方案
        for(int i = 1; i <= n; i++){ // 第 i 个硬币,从下标 1 开始
            int coin = coins[i-1]; //硬币金额
            for(int j = 0; j <= amount; j++){ //背包容量从 0 慢慢扩大到 amount
                if(j < coin) dp[i][j] = dp[i-1][j]; //当背包容量小于当前硬币金额,此硬币不用考虑
                else dp[i][j] = dp[i-1][j] + dp[i][j-coin]; //否则,一直放当前硬币
                }
        }
        return dp[n][amount]; // n 个硬币都放完,背包容量为 amount 的组合数
        }
}

 

  •  优化,利用滚动数组进行空间压缩;

  •  代码(一维数组):

class Solution {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1; //当 amount = 0 时,有 1 种方案
        for(int i = 0; i < coins.length; i++){
            int coin = coins[i]; //硬币金额
            for(int j = coin; j <= amount; j++){ //背包容量从当前硬币 coin 慢慢扩大到 amount
                dp[j] = dp[j] + dp[j-coin];
            }
        }
        return dp[amount];
    }
}

 

posted @ 2020-11-09 17:42  星海寻梦233  阅读(118)  评论(0编辑  收藏  举报