题目来源:leetcode494 目标和
题目描述:
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
提示:
数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下。
解题思路:
参考讨论区:题解
此问题可以转化为子集划分问题
将一个集合划分成两个子集A,B, 问满足sum(A) - sum(B) = Target的分法有多少种
两端同时加上集合元素和sum,得到2 * sum(A) = Target + sum ==> sum(A) = (Target + sum) / 2
已知sum(A) = sum(B) + Target >= Target, sum = sum(A) + sum(B) >= sum(A)
故sum >= Target, 且(Target + sum) % 2 == 0
接下来就是一个求子序和为某值的子集数了, 也就是经典的01背包问题
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum=0;
for(int i=0;i<nums.size();i++){
sum+=nums[i];
}
if(sum<S||(sum+S)%2!=0){
return 0;
}
else return subSets(nums,(sum+S)/2);
}
int subSets(vector<int> &nums,int sum){
int n=nums.size(),i,j;
vector<vector<int>> dp(n+1,vector<int>(sum+1,0));
for(auto& v : dp) v[0] = 1;
for(i=1;i<n+1;i++){
int val=nums[i-1];
for(j=0;j<=sum;j++){
if(j>=val){
dp[i][j]=dp[i-1][j]+dp[i-1][j-val];
}
else dp[i][j]=dp[i-1][j];
}
}
return dp[n][sum];
}
};