代码随想录Day45

今天正式开始学习用动态规划解决01背包问题,作为动规中的难点之一一定要仔细理解。

416.分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

思路:

1.本题是接触的第一道应用型的01背包问题,一开始可能会看题目涉及分割想到回溯算法,但本题回溯算法明显会比较复杂。转化一下思路后会发现既然要等和,那么首先我们要统计一下数组和,如果为奇数那么显然是不行的,如果为偶数那么得到数组和的一半就是我们要求的目标和。既然如此我们便可以把本题抽象为01背包问题,背包容量即为目标和,需要的是能够把背包装满

2.首先依然是想dp数组的含义,i表示背包的容量,dp[i]即容量为i的背包能装下的最大重量。

3.接着想递推公式,对于dp[i]来说,要么不放当前物品,即dp[i],要么就放当前物品,即dp[i - nums[i]] + nums[i],因此dp[i] = max(dp[i], dp[i - nums[i]] + nums[i])。注意本题中物品的重量等于其价值

4.接着想初始化,dp[0]显然是0,那么当i不等于0时,因为递推公式中涉及到取最大值的问题,而所有元素都能保证时非负数,因此我们全部初始化为0即可

还有一个问题,dp数组容量我们应该初始化为多少?因为题目所给的条件最多有200个元素,每个元素最大值为100,因此我们要求的目标和最大为10000,所以dp数组容量初始化为10001即可。

5.最后是遍历顺序,因为使用的是压缩后的一维dp数组,因此一定要先遍历物品再遍历背包,且背包一定要从后往前遍历。

class Solution {
public:
    bool canPartition(vector& nums) {
        int sum = 0;
        for(int i = 0; i < nums.size(); i++){
            sum += nums[i];
        }
        //若所有元素和为奇数则一定不能
        if(sum % 2 != 0) return false;

        int target = sum / 2;
        vector dp(10001, 0);

        //先遍历物品再遍历背包,背包要倒序遍历
        for(int i = 0; i < nums.size(); i++){
            for(int j = target; j >= nums[i]; j--){
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }

        return dp[target] == target;
    }
};

启发:

1.对于压缩后的一维dp数组,遍历顺序的选择非常重要。首先为什么不能先遍历背包再遍历物品?这一点其实我们看嵌套的for循环结构不难看出,如果先遍历背包再遍历物品,实际上得到的结果是我们针对一个特定的背包容量,遍历了所有物品后把能够装下的价值最大的物品装了进去,实质上是每一种容量的背包只会装一个物品,这显然不满足我们的要求。

然后是为什么背包要从后往前遍历,这一点通过举例子就会发现,如果从前往后遍历,压缩后的dp数组后面的数据会受到前面数据的影响,例如对于物品1,在背包容量为1时可能装入了一个物品1,在背包容量为2时,实质上是在背包容量为1的基础上又装入了一个物品1,此时背包中有了两个物品1,这与01背包是相悖的。

而对于未压缩的二维dp数组,每一个dp[i][j]是通过dp[i - 1][j]和dp[i - 1][j - weight[i]]推出来的,转化到矩阵之中的话实际上是由上一层(即物品的遍历)的数据推出来的,并不会受到当前层(即背包容量的遍历)的前面的数据影响,因此背包的遍历顺序没有限制。

2.本题探讨的实际上是对于我们目标的容量背包,能否用当前的物品将其装满的问题

3.关于dp数组的初始化,因为递推公式涉及到取最大值,因此将其全部初始化为0。而关于dp数组本身的容量,我们应当看我们理论上会需要的最大的背包容量来进行初始化,放在本题中就是最大的目标和。

你可能感兴趣的:(代码随想录,算法,数据结构,c++,leetcode,动态规划)