题目:
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:
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.
分析:
从题目来看,是一道很简单的使用DFS就可以求解的问题,只需要遍历每一种可能,然后计数就可以的题目,下面通过DFS求解:
// DFS
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int count = 0;
help(nums, 1, S, nums[0], count);
help(nums, 1, S, -nums[0], count);
return count;
}
void help(vector<int>& nums, int index, int S, int cur_sum, int& count) {
if (index == nums.size() - 1) {
if (nums[index] + cur_sum == S) count++;
if (-nums[index] + cur_sum == S) count++;
return;
}
help(nums, index + 1, S, cur_sum + nums[index], count);
help(nums, index + 1, S, cur_sum - nums[index], count);
}
};
但是,使用DFS求解的问题很明显,时间复杂度是指数级别的,运行速度很慢。
考虑通过动态规划算法来改进时间复杂度。
题目所描述的问题的本质,其实是在给定的数组中找出一个子集P,对P自己中的数赋予+号,同时,补集N中的数值都赋予负号,问给定的数组中存在多少种不同的集合使得两个集合中的数值之和等于给定的值。
可以写出相应的推导如下:
sum(P) + sum(N) = target;
sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) - sum(N);
2 * sum(P) = target + sum(nums);
可以看到,通过相应的推导,可以将原问题转化为在给定的数组中找出某个子集合使得这个集合中的数值之和等于 (target + sum(nums)) / 2 ,这样的集合的个数就是求解的答案。
同时,可以看到,如果target + sum(nums) 是奇数,那么这样的集合不存在。
现在,考虑如何在给定的数组中找出某个子集合使得这个集合中的数值之和等于 (target + sum(nums)) / 2的集合的个数。
这个问题与 01 背包问题很相似,只不过需要记录满足的集合的个数而已。
首先,定义变量 dp[i][v] 表示在前i个数中选集合,使得结合中的数值之和为 v 的集合的个数。
可以写出动态转移方程如下:
dp[i][v] = dp[i - 1][v] + dp[i - 1][v - nums[i]];
同时,可以发现,因为dp[i][v]只依赖与dp[i - 1]的值,因此,可以只用一维数组来表示。
dp[v] = dp[v] + dp[v - nums[i]];
代码:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
}
return (S > sum || (S + sum) % 2 != 0)? 0 : findSubset(nums, (S + sum) / 2);
}
int findSubset(vector<int>& nums, int S) {
vector<int> dp(S + 1, 0);
dp[0] = 1;
for (int i = 0; i < nums.size(); i++) {
for (int j = S; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[S];
}
};
运行结果
可以看到,使用DFS和DP的运行时间差距巨大