给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
输入: 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
注意:
数组非空,且长度不会超过20。
初始的数组的和不会超过1000。
保证返回的最终结果能被32位整数存下。
(1)动态规划。dp(i)(sum)记为数组前i个数,和为sum的方案总数,则状态方程可以记为:
dp[i][sum] = dp[i-1][sum-nums[i]] + dp[i-1][sum+nums[i]]。
这题要注意的是,S可为负数,初始数组的和不超过1000,则sum可为-1000的情况,因为数组下标这里不能为负数,所以在下标原来的基础上加1000,还要注意边界情况。
(2)直接DFS,但是这样时间复杂度比较高。
(3)转成0-1背包问题。参考leetcode中文社区
可以将这组数看成两部分,P 和 N,其中 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,就证明存在解。
令这个和为V,也就是背包容量为V时,nums[i]为第i个物品的体积,dp[V]表示在背包体积为V时,在背包中找出物品体积总和为V的方案个数。
class Solution {
public:
int findTargetSumWays(vector& nums, int S) {
int n = nums.size();
vector> dp(n, vector(2001, 0));
dp[0][nums[0] + 1000] += 1;
dp[0][-nums[0] + 1000] += 1;
for (int i = 1; i < n; i++) {
for (int sum = -1000; sum <= 1000; sum++) {
if (sum-nums[i]+1000 >= 0)
dp[i][sum+1000] = dp[i-1][sum-nums[i]+1000];
if (sum+nums[i]+1000 <= 2000)
dp[i][sum+1000] += dp[i-1][sum+nums[i]+1000];
}
}
return S > 1000 ? 0 : dp[n - 1][S + 1000];
}
};
class Solution {
public:
int res = 0;
int findTargetSumWays(vector& nums, int S) {
int n = nums.size();
dfs(nums, S, 0, 0);
return res;
}
void dfs(vector& nums, int S, int index, int sum){
if (index == nums.size()) {
if (sum == S) res++;
return;
}
dfs(nums, S, index+1, sum+nums[index]);
dfs(nums, S, index+1, sum-nums[index]);
}
};
class Solution {
public:
int findTargetSumWays(vector& nums, int S) {
int sum = accumulate(nums.begin(), nums.end(), 0);
int target = S + sum;
if (S > sum || target % 2 == 1) return 0;
target = target / 2;
vector dp(target+1);
dp[0] = 1;
for (auto cur : nums){
for (int i = target; i >= cur; --i){
dp[i] += dp[i-cur];
}
}
return dp[target];
}
};