分割等和子集 动态规划
1. 题目描述
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
- 每个数组中的元素不会超过
100
- 数组的大小不会超过
200
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
2. 题解
public boolean canPartition(int[] nums) {
int n = nums.length;
if (n < 2) {
return false;
}
int sum = 0, maxNum = 0;
for (int num : nums) {
sum += num;
maxNum = Math.max(maxNum, num);
}
if (sum % 2 != 0) {
return false;
}
int target = sum / 2;
if (maxNum > target) {
return false;
}
boolean[][] dp = new boolean[n][target + 1];
for (int i = 0; i < n; i++) {
dp[i][0] = true;
}
dp[0][nums[0]] = true;
for (int i = 1; i < n; i++) {
int num = nums[i];
for (int j = 1; j <= target; j++) {
if (j >= num) {
dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n - 1][target];
}
这里要将数组划分为两个子集,数组至少要有两个元素。
如果数组中所有元素的和sum
是奇数,不可能划分出元素和相等的两个子集;如果sum
是偶数,则令target=sum/2
,需要判断是否可以从数组中选出一些数字,使得这些数字的和等于target
。
如果数组中最大的元素maxNum
大于target
,则除了maxNum
以外的所有元素之和一定小于target
,因此也不可能划分出元素和相等的两个子集。
创建二维数组dp
,包含n
行target+1
列,其中dp[i][j]
表示从数组的[0,i]
下标范围内选取若干个正整数(可以是0
个),是否存在一种选取方案使得被选取的正整数的和等于j
。
当i = 0
时,选取nums[0]
使得j
等于1
。
当i = 1
时,有三种情况:
- 选取
nums[0]
使得j
等于1
; - 选取
nums[1]
使得j
等于5
; - 同时选取
nums[0]
和nums[1]
使得j
等于6
。
注意到当i = 1
时,要使得j
等于1
,由于nums[1]
大于1
,所以不选取nums[1]
。
因此,dp[1][1] = dp[0][1]
,即不选取nums[1]
的情况下,是否存在使得j
等于1
的选取方案。
当i = 1
时,要使得j
等于5
,由于nums[1]
小于等于5
。因此,dp[1][5] = dp[0][5] | dp[0][0]
,这里的dp[0][5]
表示不选取nums[1]
,是否存在使得j
等于5
的选取方案,dp[0][0]
表示选取nums[1]
,是否存在一种选取方案使得被选取的正整数的和等于0
。
当i = 1
时,要使得j
等于6
,由于nums[1]
小于等于6
。因此,dp[1][6] = dp[0][6] | dp[0][1]
,这里的dp[0][1]
表示选取nums[1]
,再看看i = 0
的选取方案,显然当i = 0
时,选取nums[0]
使得j
等于1
。
最后,dp[3][11]
表示从数组的[0,3]
下标范围内选取若干个正整数,是否存在一种选取方案使得被选取的正整数的和等于11
。
另一种方法:
遍历数组nums
,遍历的每个元素都选取。比如,遍历nums[0]
时选取nums[0]
,遍历nums[1]
时选取nums[1]
。
当遍历nums[0]
时,要使得j
等于1
,dp[1] |= dp[0]
。
当遍历nums[1]
时,要使得j
等于6
,dp[6] |= dp[1]
;要使得j
等于5
,dp[5] |= dp[0]
。
注意到dp[6] |= dp[1]
,它表示选取nums[1]
,是否存在一种选取方案使得被选取的正整数的和等于1
。
public boolean canPartition(int[] nums) {
int n = nums.length;
if (n < 2) {
return false;
}
int sum = 0, maxNum = 0;
for (int num : nums) {
sum += num;
maxNum = Math.max(maxNum, num);
}
if (sum % 2 != 0) {
return false;
}
int target = sum / 2;
if (maxNum > target) {
return false;
}
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int i = 0; i < n; i++) {
int num = nums[i];
for (int j = target; j >= num; --j) {
dp[j] |= dp[j - num];
}
}
return dp[target];
}
参考: