题目:
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: 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.
Note:
- The length of the given array is positive and will not exceed 20.
- The sum of elements in the given array will not exceed 1000.
- Your output answer is guaranteed to be fitted in a 32-bit integer.
思路:
1、DFS:这道题目如果用DFS求解是最直观的。也可以通过,但是效率比较低,因为其运行的时间复杂度是指数级的。
2、DP:该问题等价于:将nums中的元素划分为子集P和N,其中P中的元素都保持原值,而N中的元素都取相反数,并且P和N中的元素之和为target。例如给定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)
这样,问题就转换为如何在nums中找到一个子集P,使得sum(P) = (target + sum(nums)) / 2。需要注意到只有target + sum(nums)为偶数才有解,所以我们可以在问题的开始阶段就检测一下。该算法的时间复杂度为O(target * n),其中n是nums中元素的个数。
代码:
1、DFS:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int ret = 0;
DFS(nums, ret, 0, 0, S);
return ret;
}
private:
void DFS(vector<int> &nums, int &ret, int sum, int pos, int S) {
if (pos == nums.size()) {
if (sum == S) {
++ret;
}
return;
}
DFS(nums, ret, sum + nums[pos], pos + 1, S);
DFS(nums, ret, sum - nums[pos], pos + 1, S);
}
};
2、DP:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum < S || (sum + S) & 1) {
return 0;
}
return subsetSum(nums, (sum + S) / 2);
}
private:
int subsetSum(vector<int>& nums, int s) {
int dp[s + 1] = {0};
dp[0] = 1;
for (int n : nums) {
for (int i = s; i >= n; --i) {
dp[i] += dp[i - n];
}
}
return dp[s];
}
};