LeetCode题解——518. 零钱兑换 II(dp)

https://leetcode-cn.com/problems/coin-change-2/submissions/
题目:
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 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 位符号整数

思路

拿到题目想到dp,看到硬币无限个想到完全背包问题(每个硬币的价值为1,成本为coin,总容量为amount),然后进陷入了一个思维,导致最后都没写出来。

正解:设f(i,j)代表拿前i种硬币,可以拼凑成j元的方案数字
递推关系如下:

f(i,j) = f(i-1,j)+f(i,j-coins[j])		j >= coins[j]
f(i,j) = f(i-1,j)								i < coins[j]

初始化问题:
按照背包的思路,初始化一个二维的dp数组,行数是0~coins.length,列数是0~amount+1
当0元时,不管拿几种硬币,方案数都是1。
其他情况,当前钱数%coins[i]==0时,方案数是1。

代码

public class Solution_518 {
    public static void main(String[] args) {
        change(5, new int[]{1, 2, 5});
    }

    public static int change(int amount, int[] coins) {
        int N = amount + 1;
        int[] weight = new int[coins.length + 1];
        for (int i = 1; i < weight.length; i++) {
            weight[i] = coins[i - 1];
        }
        int[][] dp = new int[weight.length][N];
        // 初始化
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j < N; j++) {
                if (i == 0 && j != 0) {
                    dp[i][j] = 0;
                } else if (j == 0) {
                    dp[i][j] = 1;
                } else if (j % weight[i] == 0) {
                    dp[i][j] = 1;
                } else {
                    dp[i][j] = 0;
                }
            }
        }
        for (int i = 1; i < weight.length; i++) {
            for (int j = 1; j < N; j++) {
                if (j >= weight[i]) {
                    // 拿得起
                    dp[i][j] = dp[i][j - weight[i]] + dp[i - 1][j];
                } else {
                    // 拿不起
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[coins.length][amount];
    }
}

优化方案

思考f(i,j) = f(i-1,j)+f(i,j-coins[j]),当前网格用到了上一轮次的数据f(i-1,j),以及当前轮次之前更新过的数据f(i,j-coins[j])
如何用一维数组来代表当前的二维数组呢,即dp[i]代表了dp[i-1][j]dp[i-coins[j]]代替dp[i][j-coins[j]],仔细想一下,如果从i=coin开始遍历到amount+1,那如果运行到了dp[i]还没更新,dp[i]=dp[i] + dp[i-coin]中右边的dp[i]其实是上一轮的数据,dp[i-coin]其实是当前轮次之前更新过的数据。

其实和背包问题的优化方案一样,新建一个一维dp数组,大小为amount+1

代码

    public static int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1;
        for (int coin : coins) {
            for (int i = coin; i < amount + 1; i++) {
                dp[i] = dp[i] + dp[i - coin];
            }
        }
        return dp[amount];
    }

猜你喜欢

转载自blog.csdn.net/u012525096/article/details/92974388
今日推荐