Target Sum 目标和

给定一个非负整数数组,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。

注意:

  1. 数组的长度不会超过20,并且数组中的值全为正数。
  2. 初始的数组的和不会超过1000。
  3. 保证返回的最终结果为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];        
    }
};

对比思路一和思路二,效率提升还是很明显的






猜你喜欢

转载自blog.csdn.net/qq_26410101/article/details/80855135