给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11]
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
题目是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
那么只要找到集合里能够出现 sum / 2 的子集总和,就算是可以分割成两个相同元素和子集了。
例如示例1:
输入元素总和是22,要分成两个元素和相等的子集,那这两个子集各自之和肯定都是11,即sum / 2
既然这题出现在dp章节了,那肯定就是用dp来做了
但是,如果第一次遇见,应该怎么确定适用dp来解呢?可以先尝试分析一下题目,看看能不能把题目往这边靠
比如,这题中,每个构成子集的每个元素只能使用一次(关键点1)
这个描述可以联想到01背包问题,那就尝试去抽象一下:
本题可以转换为一个01背包问题,目标是给定一个数组,用数组中的元素去填满一个容量为 sum / 2 的背包(如果能填满的话,就找到了题目要求的子集)
还不够,还要继续确定什么是物品(物品重量),什么是价值
显然,能够作为物品的只有数组里的元素了,那么物品的重量就是元素数值,物品的价值也是元素数值
现在就把01背包问题套进来了,当前问题的条件如下:
ok,现在可以五部曲去分析了
以一维01背包问题为模板来解题,详见
回顾一下分析一维数组的背包问题时,dp[j]的定义:在容量为j时,背包能装下的物品的最大价值
根据上面的分析,本题中物品重量是数组元素数值,物品价值还是数组元素数值
那上面的定义可以改为:背包总容量是j,放入物品后,背包的最大重量为dp[j]
即本题dp数组的含义
举个例子,假设背包容量为target,那么dp[target]就是背包装满时的重量
当dp[target] = target,背包就装满了,此时变找到了题解子集
这里会出现一种情况:在所给的背包容量下,没有物品能装满
什么意思?举个例子,输入是数组 [1, 5, 11, 5],target是7
那么,当dp[7]的时候,背包只能放1、5,总价为6,没放满,显然这不是我们需要的结果(不满足dp数组定义),不过好在我们是倒序遍历,在之后的遍历中,dp[6] = 6,满足dp定义,并且我们也找到了一个满足条件的子集[1,5]
其实可以直接套用一维dp数组背包问题的递推公式:dp[j] = max(dp[j], dp[j - wight[i]] + value[i])
本题中,物品重量和价值都是数组元素,所以可以改成:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
其含义是:
不放入物品时,容量为j的背包最大的价值是dp[j];
放入物品时,容量为j - nums[i]的背包所背的最大价值是dp[j - nums[i]];
还是参考一维背包问题,当背包容量为0,也就是dp[0]时,里面不可能装东西
因此,dp[0] = 0;
其他部分全部初始化为0(因为题目说了数组元素全是正数),目的是为了防止倒序遍历过程中,上一层的值与当前值叠加(详见:一维dp数组如何初始化)
又因为题目给了:每个数组元素不会超过100,数组本身的大小不会超过200,且元素总和不会超过20000
那么创建dp数组时只需要取一半的大小即可
vector<int> dp(10001, 0);
都套一维dp数组的背包问题了,肯定是倒序遍历
原因详见:为什么要倒序遍历?
for(int i = 0; i < nums.size(); ++i){//遍历物品 for(int j = target; j > 0; --j){//遍历背包容量 dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]); } }
根据上面的分析可写出一下代码
步骤:
1、求数组的总和,用于求背包容量target
2、判断当前总和sum是否为偶数,不为偶数就直接返回false
3、根据题目提示定义dp数组,同时进行初始化
4、倒序遍历dp数组,先遍历物品,再遍历背包容量,计算dp[j]
5、判断dp[target] 是否等于 target
class Solution { public: bool canPartition(vector<int>& nums) { //求背包容量,也就是target //先定义一个sum用于求数组总和 int sum = 0; for(auto num : nums) sum += num; //如果sum值不能均分(不是偶数),那么一定无法分为两个和相等的子集,直接返回false if(sum % 2 == 1) return false; int target = sum / 2; //定义dp数组,同时初始化 vector<int> dp(10001, 0); // //初始化dp数组 // dp[0] = 0; //遍历dp数组(注意是倒序遍历) 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]); } } //判断是否满足题意条件 if(dp[target] == target) return true; return false; } };