题目
给定一个非负整数数组,a1, a2, ..., an
, 和一个目标数,S
。现在你有两个符号 +
和 -
。对于数组中的任意一个整数,你都可以从 +
或 -
中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S
的所有添加符号的方法数。
示例 1:
输入: 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
一共有5种方法让最终目标和为3。
注意:
- 数组的长度不会超过20,并且数组中的值全为正数。
- 初始的数组的和不会超过1000。
- 保证返回的最终结果为32位整数。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
暴力穷举
最初的思路就是暴力了,把所有情况都考虑一遍,只要数据量不大,都能搞定(笑,这里的数组长度不超过20,可以尝试一下。时间复杂度是 O ( 2 n ) O(2^n) O(2n),指数级时间复杂度。
class Solution {
public:
vector<int> nums;
int S;
int sumWay = 0;
int findTargetSumWays(vector<int>& nums, int S) {
this->nums = nums;
this->S = S;
dfs(0, 0);
return sumWay;
}
void dfs(int step, int sum) {
if (step == nums.size() - 1) {
if (sum + nums[step] == S) sumWay++;
if (sum - nums[step] == S) sumWay++;
return;
}
dfs(step + 1, sum + nums[step]);
dfs(step + 1, sum - nums[step]);
}
};
动态规划
考虑其他解法的话,这个求和问题乍一看就像背包问题,我们可以把它变为背包问题~
原问题可以理解为:把nums
数组分为两个互补子集P
、N
,使两个集合的差为S
。
sum(P) - sum(N) = S
sum(P) + sum(N) + sum(P) - sum(N) = sum(P) + sum(N) + S
2 * sum(P) = S + sum(nums)
经过如上推倒,题目转变为:从nums
数组中找到子集P
,其和等于target = (S + sum(nums)) / 2
。
这就转变为0/1背包问题了,可以使用动态规划解决。
dp[i]
表示nums
的所有子集中,和为i
的子集个数。
初始条件
对于target
为0
的情况,肯定有且只有一个情况,那就是所有的数都不取,即空集。
状态转移方程
d
p
[
i
]
=
d
p
[
i
]
+
d
p
[
i
−
n
u
m
]
dp[i] = dp[i] + dp[i - num]
dp[i]=dp[i]+dp[i−num]
两个dp[i]
我们称之为new dp[i]
与old dp[i]
,分别对应本次循环的num
与下次循环的num
。
old dp[i]
,为不取本次循环num
时和为i
的子集的个数,dp[i-num]
为取num
时和为i
的子集的个数,而new dp[i]
则是没有取下次循环时num
时和为i
的子集的个数,相当于下次循环时的old dp[i]
。
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int ans = 0, sum = 0;
for (int n : nums) sum += n;
// 根据推到的方程,如果不是偶数,则一定没有结果
if (sum < S || (sum + S) % 2 == 1) return 0;
int target = (sum + S) >> 1;
vector<int> dp(target + 1, 0);
dp[0] = 1; //有且有只有一种情况可以使和为0:所有值都不取
// dp[i-n]为取n时和为target的解
for (int n : nums) {
for (int i = target; i >= n; i--) {
dp[i] = dp[i] + dp[i - n];
}
}
return dp[target];
}
};