【动态规划之背包问题】——完全背包问题(518. 零钱兑换 II)

完全背包问题

518. 零钱兑换 II

题目描述

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

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

解题思路

转换为背包问题的描述形式

有一个背包, 最打容量为 amount , 有一系列物品 coins , 每个物品的重量为 coins[i] , 每个物品的数量有限。 请问有多少种方法, 能够把背包恰好装满?

动态规划的套路:

  1. 要明确两点, 「状态」 和「选择」 。
    状态有两个, 就是「背包的容量」 和「可选择的物品」 。
    选择就是「装进背包」 或者「不装进背包」 。

框架:

for 状态1 in 状态1的所有取值:
	for 状态2 in 状态2的所有取值:
		for ...
			dp[状态1][状态2][...] = 计算(选择1, 选择2...)
  1. 要明确 dp 数组的定义
    • dp[i][j] 的定义如下:若只使用前 i 个物品, 当背包容量为 j 时, 有 dp[i][j] 种方法可以装满背包

      • 即 若只使用 coins 中的前 i 个硬币的面值, 若想凑出大额 j , 有 dp[i][j] 种凑法
    • 答案就是 dp[N][amount], N 为 coins 数组的大小

    • base case 为 dp[0][..] = 0, dp[..][0] = 1 。 因为如果不使用任何硬币面值, 就无法凑出任何金额; 如果凑出的目标金额为 0, 那么“无为而治”就是唯一的一种凑法。

细化后的框架:

int dp[N+1][amount+1]
dp[0][..] = 0
dp[..][0] = 1

	for i in [1..N]:
		for j in [1..amount]:
			把物品 i 装进背包,
			不把物品 i 装进背包
return dp[N][amount]
  1. 根据「选择」 , 思考状态转移的逻辑
    dp[i][w] 表示: 对于前 i 个物品, 当前背包的容量为 w 时, 这种情况
    下可以装下的最大价值是 dp[i][w]
    • 如果你没有把这第 i 个物品装进背包, 那么很显然, 不使用 coins[i] 这个面值的硬币, 那么凑出面额 j 的方法数 dp[i][j] 应该等于 dp[i-1][j] ,继承之前的结果。
    • coins[i] 这个面值的硬币, 那么 dp[i][j] 应该等于 dp[i][j-coins[i-1]] 。其中:dp[i][j-coins[i-1]] 也不难理解, 如果你决定使用这个面值的硬币, 那么就应该关注如何凑出金额 j - coins[i-1]

dp[i][j] 是「共有多少种凑法」 , 所以dp[i][j] 的值应该是以上两种选择的结果之和

for (int i = 1; i <= n; i++) {
	for (int j = 1; j <= amount; j++) {
		if (j - coins[i-1] >= 0)
			dp[i][j] = dp[i - 1][j]
						+ dp[i][j-coins[i-1]];
return dp[N][W]

完整代码求解为

二维数组:

   public static int change_1(int amount,int[] coins){
        int n = coins.length;
        int[][] dp = new int[n + 1][amount + 1];
        //base case
        for (int i = 0; i <= n; i++) {
            dp[i][0] = i;
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= amount; j++) {
                if (j - coins[i - 1] >= 0){
                    dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
                }else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[n][amount];
    }


一维数组:

 //状态压缩,二维变一维
    public static int change(int amount,int[] coins) {
        int n = coins.length;
        int[] dp = new int[amount + 1];
        dp[0] = 1;//base case
        for (int i = 0; i < n; i++) {
            for (int j = 1; j <= amount ; j++) {
                if (j - coins[i] >= 0){
                    dp[j] = dp[j] + dp[j - coins[i]];
                }
            }
        }
        return dp[amount];
    }

参考:labuladong

posted @ 2021-03-28 21:37  your_棒棒糖  阅读(80)  评论(0编辑  收藏  举报