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.
Example 1:
Input: nums is [1, 1, 1, 1, 1], S is 3.
Output: 5Explanation:
-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.
这道题乍一看可能显得有些难,有些不知所措,不知道从何下手。可能第一想法是回溯法,先把数组中所有的数进行相加,如果超过目标和target,再将其中的某个数改成减法、使所有数相加减为target,直到找到所有情况为止。但这样的话其实本质是个递归的过程,运行是非常慢的,那有没有什么更好的做法呢?
其实这道题我们可以这样想:这道题等价于找到一个子集,其中的数全部为正数;其它剩余的数为负数,使正数之和与负数之和相加等于目标和target。
令P为正子集,N为负子集
例如:给定nums=[1,2,3,4,5]和target=3,那么一个可能的解决方案是+1-2+3-4+5=3,很显然这里的正子集是P=[1,3,5],负子集是N=[2,4]。
那么让我们看看如何将其转换为子集总和问题:
易得sum(P)-sum(N)=target
那么可有sum(P)+sum(N)+sum(P)-sum(N)=target+sum(P)+sum(N)成立
即2*sum(P)=target+ sum(nums)
所以原来的问题已被转换为子集和问题如下:
找出num的一个子集P,使得sum(P)=(target+ sum(nums))/2
注意上面的公式已经证明了target+ sum(nums)必须是偶数
我们可以利用这个事实很快地判断是否有解。
接下来就是如何求解子集和的问题了,这里用到了动态规划DP——0/1背包的思想。用一个数组dp来表示每个数可以被分解为前面某些数之和的种数,刚开始所有数都没有被选中,全部初始化为0,dp[0]初始化为1。从sum(P)开始由后往前遍历,每个数num要么被选要么被抛弃,因此有dp[i]+=dp[i-num]。这样遍历完成后最后的一个元素就是我们所要的结果。
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum < S || (sum + S) % 2 == 1) {
return 0;
}
return subsetSum(nums, (sum + S) / 2);
}
int subsetSum(vector<int>& nums, int S) {
int *dp = new int[S + 1];
dp[0] = 1;
for (int i = 1; i <= S; i++) {
dp[i] = 0;
}
for (int num : nums) {
for (int i = S; i >= num; i--) {
dp[i] += dp[i - num];
}
}
return dp[S];
}
};
以上是我对这道问题的一些想法,有问题还请在评论区讨论留言~