518. 零钱兑换 II(完全背包一维二维的理解)

518. 零钱兑换 II

2021.6.10每日一题,完全背包一维二维的理解

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


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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change-2
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

一个硬币可以使用多次,完全背包问题
dp[i]表示可以组成面额为i的组合的个数
外层循环遍历coins中的值,内层循环遍历金额总数,在计算 dp[i] 的值时,可以确保金额之和等于 i 的硬币面额的顺序,由于顺序确定,因此不会重复计算不同的排列。例如对于金额3,因为硬币值的顺序是固定的,因此先1后2,只有一种情况
而如果两层循环反过来了,就是求排列数,因为第一层循环是遍历金额,第二层循环是遍历硬币,例如对于3这个金额,你可以在1后面选择2,也可以在2后面选择1,就是两种情况

class Solution {
    public int change(int amount, int[] coins) {
        //完全背包,每个物品可以使用无限次
        //想想,一维,正序遍历
        int l = coins.length;
        int[] dp = new int[amount + 1];
        //初始化,总金额为0的时候,有一种方式
        dp[0] = 1;

        for(int i = 0; i < l; i++){
            for(int j = 0; j <= amount; j++){
                if(j >= coins[i])
                    dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];

    }
}

变成二维的,代码如下:
这里有个与平时二维dp不同的一点,就是当面额大于等于当前硬币值的时候,需要加的不是dp[i - 1][j - coins[i - 1]],而是加的是dp[i][j - coins[i - 1]],
这个点怎么理解呢,我认为是因为一个硬币可以使用多次,而不是0-1背包中只能使用一次了,因此之前使用过这个硬币了,现在还可以继续使用。因此要从dp[i]来转移

class Solution {
    public int change(int amount, int[] coins) {
        //写个二维的,再品一品
        int l = coins.length;
        int[][] dp = new int[l + 1][amount + 1];
        //初始化,总金额为0的时候,有一种方式
        for(int i = 0; i <= l; i++){
            dp[i][0] = 1;
        }

        for(int i = 1; i <= l; i++){
            for(int j = 1; j <= amount; j++){
                dp[i][j] = dp[i - 1][j];
                if(j >= coins[i - 1])
                    dp[i][j] += dp[i][j - coins[i - 1]];
            }
        }
        return dp[l][amount];

    }
}

再看三叶姐写的二维的,这种二维的更好理解,每个都硬币使用无数次,所以遍历每个硬币使用的情况

class Solution {
    public int change(int cnt, int[] cs) {
        int n = cs.length;
        int[][] f = new int[n + 1][cnt + 1];
        f[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            int val = cs[i - 1];
            for (int j = 0; j <= cnt; j++) {
                f[i][j] = f[i - 1][j];
                for (int k = 1; k * val <= j; k++) {
                    f[i][j] += f[i - 1][j - k * val];  
                }
            }
        }
        return f[n][cnt];
    }
}

作者:AC_OIer
链接:https://leetcode-cn.com/problems/coin-change-2/solution/gong-shui-san-xie-xiang-jie-wan-quan-bei-6hxv/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

完全背包二维变成一维的,还是比较难理解的,很多题解都是之直接变过来了,最多写一下一维的dp数组遍历的过程,严格证明的几乎没有
三叶姐是看到的第一个证明为什么将01背包时dp一维数组遍历顺序反过来就变成了完全背包,学习一下:
https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247486107&idx=1&sn=e5fa523008fc5588737b7ed801caf4c3&chksm=fd9ca184caeb28926959c0987208a3932ed9c965267ed366b5b82a6fc16d42f1ff40c29db5f1&token=660810714&lang=zh_CN#rd

你可能感兴趣的:(LeetCode,leetcode,动态规划)