给定一个非负整数数组,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位整数。
思路一,采用递归的解法,即暴力求解法,递归遍历每一种情况,如果到最后一个数和为s,那么把res++,否则就继续搜索。
参考代码:
class Solution {
public:
void findTargetSumWaysCore(int start, int end, int S,int &res, vector<int>& nums,int sum) {
if (start == (end - 1)) {
int tmp = sum;
sum += nums[start];
if (sum == S) {
res++;
}
sum = tmp;
sum -= nums[start];
if (sum == S) {
res++;
}
}
if (start < end) {
for (int i = 0; i < 2; i++) {
if (i == 0) {
findTargetSumWaysCore(start + 1, end, S, res, nums, sum + nums[start]);
}
else {
findTargetSumWaysCore(start + 1, end, S, res, nums, sum - nums[start]);
}
}
}
}
int findTargetSumWays(vector<int>& nums, int S) {
if (nums.size() == 0) {
return 0;
}
int res = 0;
int sum = 0;
findTargetSumWaysCore(0, nums.size(), S,res,nums,sum);
return res;
}
};
但是上面这种方法简单粗暴,时间复杂度是(2^n)n是数字的个数,所以我们采用优化的算法
思路二:这种想法比较巧妙,一开始我也想到用01背包求解,但是不知道怎么处理负数的问题以及如何奇数的问题。我们来看如下例子:
nums = {1,2,3,4,5}, target=3, 一种可行的方案是+1-2+3-4+5 =3
该方案中数组元素可以分为两组,一组是数字符号为正(P={1,3,5}),另一组数字符号为负(N={2,4})
因此: sum(1,3,5) - sum(2,4) = target
sum(1,3,5) - sum(2,4) + sum(1,3,5) + sum(2,4) = target + sum(1,3,5) + sum(2,4)
2sum(1,3,5) = target + sum(1,3,5) + sum(2,4)
2sum(P) = target + sum(nums)
sum(P) = (target + sum(nums)) / 2
由于target和sum(nums)是固定值,因此原始问题转化为求解nums中子集的和等于sum(P)的方案个数问题
求解nums中子集合只和为sum(P)的方案个数(nums中所有元素都是非负)可以采用01背包来求解,我们初始化dp的长度为(target+sum(nums))/2+1,并且dp[0]=1.递推公式为:
dp[j] =dp[j] + dp[j - nums[i]];
dp[j]的意思是当累加的和是j时子集和的个数,那么dp[j]由两部分组成,如果不选择第i个元素,那么等于第i-1个元素背包容量为j的子集的和。如果选择第i个元素,那么等于第i-1个元素背包容量为j-nums[i]时的子集和个数。然后把考虑第i个元素和不考虑第i个元素的情况都加入dp[j]的计算中。还有一点需要注意:如果累加和(target + sum(nums))是奇数或者target>sum(nums),那么返回0
参考代码:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
if (nums.size() == 0) return 0;
int sum = 0;
for (int i : nums) {
sum += i;
}
if (((sum + S) & 1) || (sum + S)<0 || S>sum) return 0;
sum = S + sum;
sum = sum / 2;
vector<int> dp(sum + 1, 0);
dp[0] = 1;
for (int i = 0; i < nums.size(); i++) {
for (int j = sum; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[sum];
}
};
对比思路一和思路二,效率提升还是很明显的