题目描述:给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
解题思路:
-
可以看成一个背包大小为 sum/2 的 0-1 背包问题。
class Solution {
public boolean canPartition(int[] nums) {
int sum = sum(nums);
if(sum%2!=0)
return false;
int w = sum / 2;
boolean dp[] = new boolean[w+1];
dp[0] = true;
for(int num:nums){
for(int j = w;j>=num;j--)
dp[j] = dp[j]||dp[j-num];
}
return dp[w];
}
public int sum(int[] nums){
int sum = 0;
for(int x:nums)
sum += x;
return sum;
}
}
拓展——改变一组数的正负号使得它们的和为一给定数
题目描述:给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
解题思路:
- 该问题可以转换为 Subset Sum 问题,从而使用 0-1 背包的方法来求解。
- 可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
因此只要找到一个子集,令它们都取正号,并且和等于 (target + sum(nums))/2,就证明存在解。
class Solution {
public int findTargetSumWays(int[] nums, int S) {
int sum = sum(nums);
if(S>sum||(sum+S)%2!=0)
return 0;
int w = (sum + S)/2;
int[] dp = new int[w+1];
dp[0] = 1;
for(int num:nums){
for(int j = w;j>=num;j--)
dp[j] = dp[j] + dp[j-num];
}
return dp[w];
}
public int sum(int[] nums){
int sum = 0;
for(int num:nums)
sum += num;
return sum;
}
}