Day44 | 完全背包, 518. 零钱兑换 II, 377. 组合总和 Ⅳ

Day44 | 完全背包, 518. 零钱兑换 II, 377. 组合总和 Ⅳ

完全背包

  完全背包和01背包之间的差别就在于,01背包每个物品只可以放一次,而完全背包可以放无数次。因此,与01背包中的一维滚动不同,完全背包不需要在第二次循环时候从右向左去遍历。以此来避免重复取到物品带来的状态提前转移问题。

  此外,对于完全背包,如果求的是背包容量下的最大价值,两层循环并无不同,因为最后都是得到一个最大值即可。但如果是装满背包的方案问题则存在差异。当第一层循环为物品价值,第二层循环为背包容量的时候,由于每层先对物品进行循环,所以当背包容量判定的时候,物品价值的顺序是确定的。当前状态一定是对当前物品的价值信息进行更新,而不可能再对之前已经迭代过的物品信息进行更新。因此是一个组合问题。

  而当第一层循环为背包容量的时候,在循环物品价值时候,每一次物品价值的更新都可能会对当前的状态进行更新。即假设有一个dp[5],那么当物品价值为1的时候和物品价值为5的时候,都会对方案进行一次更新相加。因此,此时就是排列问题的解法。

背包DP思路

  因此,只需要将原先二层循环的倒序改为正序即为完全背包问题的代码范式。

零钱兑换II

LeetCode题目:https://leetcode.cn/problems/coin-change-ii/

  零钱兑换即是装满背包方法的组合问题,因此采用先物品价值在背包容量进行迭代即可。

  因此,由以上推理,问题可以化为:当可以取到x的值时,有多少种方法组成。即此时dp的容量上限为x,dp[i]意为容量为x时最多有多少种组合方法。因此可以看作一次性可以上i个台阶的上楼梯问题,所以递推公式为dp[j] += dp[j - nums[i]];同时,注意初始化的时候,当容量为0的时候一定有一种组合方法,其他则默认为0。

  最终两次循环第一轮循环物品价值,第二轮循环背包容量,代码如下:

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; j++) {
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
};

组合总和 Ⅳ

LeetCode题目:https://leetcode.cn/problems/ones-and-zeroes/

  该问题与上一道题目的零钱兑换很相似,唯一的区别就在于此时顺序不同的也可以看做是新的方法。

  因此,由以上推理,使用排列时的完全背包方法即可。

  最终两次循环第一轮循环背包的容量,第二轮循环物品的价值,保证在同一个背包容量下每一种物品价值开头的方法都可以重复取一次,代码如下:

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<unsigned int> dp(target + 1, 0);
        dp[0] = 1;
        for (int i = 0; i<=target; i++) {
            for (int j = 0; j < nums.size(); j++) {
                if (nums[j] <= i) {
                    dp[i] += dp[i - nums[j]] ;
                }
            }
        }
        return dp[target];
    }
};

你可能感兴趣的:(数据结构,算法,动态规划,leetcode)