09_分割等和子集

416. 分割等和子集

题目难易:中等

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

注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200

示例 1:

  • 输入: [1, 5, 11, 5]
  • 输出: true
  • 解释: 数组可以分割成 [1, 5, 5] 和 [11].

示例 2:

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

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

【思路】

这道题目是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。那么只要找到集合里能够出现 sum / 2 的子集总和,就算是可以分割成两个相同元素和子集了。

动态规划五部曲:

1、确定dp数组及其下标的含义

01背包找那个,dp[j]表示:容量为j的背包,所背的物品价值最大可以为dp[j]。

本题中每一个元素的数组既是重量,也是价值。

套到本题,dp[j]表示背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]

那么如果背包容量为target, dp[target]就是装满背包之后的重量,所以当 dp[target] == target 的时候,背包就装满了。

2、确定递推公式

01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。

所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

3、dp数组如何初始化

在01背包,一维dp如何初始化,已经讲过,

从dp[j]的定义来看,首先dp[0]一定是0。

如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。

这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了

本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。

代码如下:

java 复制代码
//题中说:每个数组中的元素不会超过100,数组的大小不会超过200
//总和不会大于20000,背包最大只需要其中一般,所以10001大小就可以了
int[] dp = new dp[10001];

4、确定遍历顺序

如果使用一维dp数组,先遍历物品,在遍历背包。

代码如下:

java 复制代码
//开始01背包
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]);
	}
}

5、举例推导dp数组

dp[j]的数值一定是小于等于j的。

如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。

完整代码如下:

java 复制代码
class Solution {
    public boolean canPartition(int[] nums) {
        if(nums == null || nums.length == 0) return false;
        int n = nums.length;
        int sum = 0;
        for(int num : nums) {
            sum += num;
        }
        //总和为奇数,不能平分
        if(sum % 2 != 0) return false;
        int target = sum / 2;
        int[] dp = new int[target + 1];
        for(int i = 0; i < n; i++) {  //先遍历物品
            for(int j = target; j >= nums[i]; j--) {
                //物品 i 的重量是 nums[i],其价值也是 nums[i]
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
           
            //剪枝一下,每一次完成内部的for-loop,立即检查是否dp[target] == target,优化时间复杂度(26ms -> 20ms)
            if(dp[target] == target)
                return true;
        }
        return dp[target] == target;
    }
}

总结

这道题目就是一道01背包应用类的题目,需要我们拆解题目,然后套入01背包的场景。

01背包相对于本题,主要要理解,题目中物品是nums[i],重量是nums[i],价值也是nums[i],背包体积是sum/2。

看代码的话,就可以发现,基本就是按照01背包的写法来的。