给定一个非负整数数组,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
一共有5种方法让最终目标和为3。
提示:
- 数组非空,且长度不会超过 20 。
- 初始的数组的和不会超过 1000 。
- 保证返回的最终结果能被 32 位整数存下。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
——题目难度:中等
dfs解题
class Solution {
private:
int ans;
public:
void dfs(vector<int>& nums, int pos, long long cur_sum) {
if (cur_sum == 0 && pos == nums.size()) {
ans++;
return ;
}
if (pos == nums.size()) return ;
dfs(nums, pos + 1, cur_sum - nums[pos]);
dfs(nums, pos + 1, cur_sum + nums[pos]);
}
int findTargetSumWays(vector<int>& nums, int S) {
ans = 0;
dfs(nums, 0, S);
return ans;
}
};
运行结果很慢
还可以使用0-1背包,动态规划的解法。
- 此问题可以转化为子集划分问题,将一个集合划分成两个子集A,B, 问满足sum(A) - sum(B) = Target的分法有多少种。(其中sum(A)表示正数的集合(未变号的数的集合),sum(B)表示已经变号的数相加后 把负号提出来舍去负号的数的集合)
- 所以 sum = sum(A) + sum(B),其中sum为原始nums中各个正数相加的和。
- 将此式代入上式的等式两边,消去sum(B),有2 * sum(A) = Target + sum,所以问题又可以变成满足sum(A) = (Target + sum) / 2的分法有多少种。
且易知如果存在分法,(Target+sum)必须为偶数,且sum >= Target。
下面代码
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for(int num : nums)
sum += num;
if (sum < S || (S+sum) % 2 != 0) return 0;
sum = (S + sum) / 2;
//dp[i][j] = x 表示对于nums中前i个元素,相加达到j的最大方法数
vector<vector<int>> dp(nums.size()+1, vector<int>(sum+1, 0));
//初始化:dp[0][1]~dp[0][nums.size()]都为0
dp[0][0] = 1; //当前于nums中前0个元素时,相加达到0的方法只有一个(就是不加上nums中前i个元素)
for(int i = 1; i <= nums.size(); i++) {
for(int j = 0; j <= sum; j++) {
if (j >= nums[i-1]) { //此时最大方法数为:装第i个元素 + 不装第i个元素,所以得继承两种可能的方法
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];
} else dp[i][j] = dp[i-1][j]; //装不下(不装当前元素),所以只能继承当前i-1个元素时且背包空间为j时的最优状态
}
}
return dp[nums.size()][sum];
}
};
运行结果挺快的