1. 题目描述
You are given a list of non-negative integers, a1, a2, …, an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol. Find out how many ways to assign symbols to make sum of integers equal to target S.
【翻译过来】:给了一个非负数的矩阵,里面的元素是[a0, a1, a2……]和一个目标值S,现在要在这些元素之间加入+或者-,使得里面的元素之间运算结果能够等于目标值S,求一共有多少种加入+或者-的方法。
2. 样例
Input: nums is [1, 1, 1, 1, 1], S is 3.
Output: 5
Explanation:
-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
There are 5 ways to assign symbols to make the sum of nums be target 3.
3. 分析
拿到题目之后,即可判断出是自己不熟悉的动态规划类问题。表面上来看是在数组元素中间插入+或者-使得数组元素运算等于目标值,实际上对这个问题进行变换,可以将问题简化。
【变换】:
- 假设原数组为S,目标值为target,那么原数组必然可以分成两个部分,一个部分里面的元素前面需要加-,即运算的时候应该是做减法,另一个部分里面的元素前面需要加+,即运算的时候应该是做加法;
- 我们将做加法部分的数组记为P,做减法部分的数组记为N,举个例子,例如S = {1,2,3,4,5},target = 3,那么有一种可以是1-2+3-4+5,即P = {1,3,5},N = {2,4};
- 于是我们可以知道:target = sum(P) - sum(N);
- 那么sum(P) + sum(N) + sum(P) - sum(N) = sum(S) + target = 2sum(P);
- 那么sum(P) = [target + sum(S)] / 2;
根据以上的推导,我们可以得到这样的结论:我们需要找到这样一个子序列P,使得子序列之和等于原序列之和与目标值的和的一半,我们需要找到这样子序列的数目。
显而易见,如果原序列之和与目标值的和为奇数,肯定不存在这样的子序列,如果原序列之和小于目标值,也肯定不存在(因为这种情况下,即使我们填的都是+,让所有元素相加都小于目标值,肯定不存在满足条件的解决方案)。
排除了以上的两种特殊情况之后,我们接下来研究的就是在原数组中找到之和符合条件的子序列个数,非常明显DP的方法。
4. 源码
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for (int i = 0; i < (int)nums.size(); i++) {
sum += nums[i];
}
if (sum < S || (sum + S) % 2 != 0) {
return 0;
}
int target = (S + sum) / 2;
int dp[target + 1];
for (int i = 0 ; i < target+1; i++) {
dp[i] = 0;
}
dp[0] = 1;
for (int i = 0; i < (int)nums.size(); i++) {
for (int j = target; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[target];
}
};5. 心得
推导的方案是参照讨论区的大佬们的思路,的确很厉害。但是发现自己对于DP还是不是很擅长,即使找到了问题的简化方案,找到满足条件子序列的个数使用dp[]数组,仍然不能完全理解代码的思路。