分割等和子集
2024年10月9日约 777 字大约 3 分钟
分割等和子集
题意
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
思路
本题是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
那么只要找到集合里能够出现 sum / 2
的子集总和,就算是可以分割成两个相同元素和子集了。
类比于 背包问题:有 件物品和一个最多能背重量为 的背包。第 件物品的重量是 w[i]
,得到的价值是 v[i]
。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
对应到本题中:
- 背包的体积为
sum / 2
- 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
- 背包如果正好装满,说明找到了总和为
sum / 2
的子集。 - 背包中每一个元素是不可重复放入。
动规分析:
- 本题中每一个元素的数值既是重量,也是价值。
定义 DP 数组:dp[j]
表示背包总容量是 的情况下,最大重量为dp[j]
。(注意:已经过状态压缩) - 判断条件:
如果背包容量为 target,dp[target]
就是装满背包之后的重量,所以当dp[target] == target
的时候,背包就装满了。 - 确定递推公式:
背包的递推公式为:dp[j] = max(dp[j], dp[j - w[i]] + v[i])
类似地,本题的递推公式为:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
- 初始化:
从dp[j]
的定义来看,首先dp[0]
一定是 。
如果题目给的价值都是正整数,那么非 下标都初始化为 就可以了,如果题目给的价值有负数,那么非 下标就要初始化为负无穷。
这样才能让 DP 数组在递推的过程中取得最大的价值,而不是被初始值覆盖了。
所以本题中非 下标的元素初始化为 就可以了。 - 确定遍历顺序:
如果是二维 DP 数组,物品遍历和背包遍历的顺序可以颠倒;
但如果是一维 DP 数组,那么物品遍历放在外层,遍历背包放在内层,且内层循环必须倒序遍历!
这样才能保证前面的状态不会被后面覆盖。
代码:
class Solution {
public boolean canPartition(int[] nums) {
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];
Arrays.fill(dp, 0);
for (int i = 0; i < n; i++) {
for (int j = target; j >= nums[i]; j--){
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}