「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战」。
题目
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
思路
要使得两个子集的元素和相等,即任何一个集合的和都是总和的一半。由于题目明确了只包含正整数,所以如果总和sum是奇数,肯定无法分割。如果是偶数,题目就转换成了01背包的变种,就是每个数字num只能用0次或者1次,求是否能把大小为sum/2的背包刚好装满。
我们定义一个二维数组dp,dp[i][j]代表从num[0] ~ num[i]挑选数字,能否刚好组成和为j。对于dp[i][j],我们可以分成以下2种情况:
- j < num[i],这时num[i]肯定不能选,所以显然有dp[i][j] = dp[i-1][j]
- j >= num[i],这时num[i]可以选也可以不选,如果选择num[i],dp[i][j] = dp[i-1][j-num[i]];如果不选num[i],dp = [i-1][j],综合起来,就是 dp[i][j] = dp[i-1][j-num[i]] || dp[i-1][j] 所以,我们的状态转移方程就是:
dp[i][j] = dp[i-1][j-num[i]] || dp[i-1][j], j >= num[i]
dp[i][j] = dp[i-1][j], j < num[i]
观察状态转移方程,dp[i][j]只跟dp[i-1][x]有关,所以这里可以进行空间优化,只要定义一个一维数组dp[sum/2+1],每次滚动覆盖即可。当然这里要注意,内层循环的时候,j的值要从target递减来遍历,保证此时的dp[j-num[i]]还是i-1状态的dp[j-num[i]],而不是i状态下的更新后的值。
Java版本代码
class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
if (len < 2) {
return false;
}
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum % 2 == 1) {
return false;
}
int target = sum >> 1;
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int num : nums) {
for (int j = target; j >= num; j--) {
dp[j] = dp[j]||dp[j-num];
}
}
return dp[target];
}
}