给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 +
和 -
。对于数组中的任意一个整数,你都可以从 +
或 -
中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例 1:
输入: 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位整数存下。
思路:正数的总和为P, 负数的总和为N, P-N=sum, P+N=target. 因此P=(sum+target)/2. 问题转化为找子集的和为(sum+target)/2的子集总数。零一背包问题,动态规划解决。dp[i][j]表示前i个数的子集和等于j的子集的个数。请参考416. 分割等和子集
1、如果没选nums[i]:dp[i][j] = dp[i-1][j]
2、如果选了nums[i]:dp[i][j] = dp[i-1][j-nums[i]]。其中dp[i-1][j-nums[i]]表示前i-1个数能够成的子集和为j-nums[i]的子集的个数
因此,状态转移方程为dp[i][j]= dp[i-1][j] + dp[i-1][j-nums[i]]。
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for (auto x : nums) sum += x;
if (sum<S || (sum + S) % 2 == 1) return 0;
sum = (sum + S) / 2;
vector<int>dp(sum + 1);
dp[0]=1;
for (auto x:nums){
for (int j = sum; j >= x; --j){
dp[j] += dp[j - x];
}
}
return dp[sum];
}
};