给定一个正整数数组,问能否把其分成两个子数组,使这两个子数组的和相等。
分析:先把数组和sum求出来,题目实际上是求是否存在子数组,使该子数组的和等于sum的一半。
方法一:用sum的一半,不断减去数组中元素,把所有可能的差值算出来,看是否有等于0。该方法时间复杂度和空间复杂度较高。
方法二:递增法,分别算出子数组长度分别为1,2 ... n 时,看子数组的和是否有等于sum的一半的情况。
方法三:作为0/1背包问题求解。用 dp[i][j] 表示前 i 个元素能否得到和 j。如果用数组的前 i 个元素能得到和为 j , 则 dp[i][j] 为true,否则为false。
初始条件:dp[i][0] = true,即任意 i 都可以得到和为0的情况;且 dp[0][j] = false(j = 1...n ),因为元素都为正数。
转换过程:对于元素 i ,如果不加该元素,则dp[i][j] = dp[i - 1][j],即如果前i-1个元素能得到j,那么前i个元素也可以;如果加上该元素,则 dp[i][j] = dp[i-1][j-nums[i]],即如果前 i-1 个元素可以得到 j-nums[i],那么加上 nums[i] 就可以得到 j。
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((sum & 1) == 1) {
return false;
}
sum /= 2;
int n = nums.length;
boolean[][] dp = new boolean[n+1][sum+1];
for (int i = 0; i < dp.length; i++) {
Arrays.fill(dp[i], false);
}
dp[0][0] = true;
for (int i = 1; i < n+1; i++) {
dp[i][0] = true;
}
for (int j = 1; j < sum+1; j++) {
dp[0][j] = false;
}
for (int i = 1; i < n+1; i++) {
for (int j = 1; j < sum+1; j++) {
dp[i][j] = dp[i-1][j];
if (j >= nums[i-1]) {
dp[i][j] = (dp[i][j] || dp[i-1][j-nums[i-1]]);
}
}
}
return dp[n][sum];
}
还可以进一步对空间进行优化:
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((sum & 1) == 1) {
return false;
}
sum /= 2;
int n = nums.length;
boolean[] dp = new boolean[sum+1];
Arrays.fill(dp, false);
dp[0] = true;
for (int num : nums) {
for (int i = sum; i > 0; i--) {
if (i >= num) {
dp[i] = dp[i] || dp[i-num];
}
}
}
return dp[sum];
}