给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 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
+1 + 1 + 1 + 1 - 1 = 3
数组 nums \textit{nums} nums 的每个元素都可以添加符号 + + + 或 − - −,因此每个元素有 2 种添加符号的方法,nn 个数共有 2 n 2^n 2n 种添加符号的方法,对应 2 n 2^n 2n 种不同的表达式。当 n 个元素都添加符号之后,即得到一种表达式,如果表达式的结果等于目标数 target,则该表达式即为符合要求的表达式。
可以使用回溯的方法遍历所有的表达式,回溯过程中维护一个计数器 ans,当遇到一种表达式的结果等于目标数 target 时,将 ans 的值加 1。遍历完所有的表达式之后,即可得到结果等于目标数 ans 的表达式的数目。
class Solution {
public:
void backtrack(int idx, vector<int>&nums, int target, int& ans){
if(idx == nums.size() ){
if(target == 0) ++ans; // 表达式完成且差值为0
return;
}
//for(int i = idx; i < nums.size(); ++i){ // 逐个往下递归遍历即可,不需要for循环
backtrack(idx+1, nums, target-nums[idx], ans); // +nums[idx],target -= nums[idx]
backtrack(idx+1, nums, target+nums[idx], ans); // -nums[idx],target += nums[idx]
//}
}
int findTargetSumWays(vector<int>& nums, int target) {
int ans = 0;
backtrack(0, nums, target, ans);
return ans;
}
};
元素前添加 + 或 -,则一定有 left组合 - right组合 = target;
则 left - (sum - left) = target;
left = (sum + target) / 2;
其中sum 和 target 已知且固定,因此存在唯一的和为 left 的组合。
此时问题就、转化为在集合nums中找出和为 left 的组合数量,且不可重复取值。
dp[i][j]
表示:在数组 nums 的前 i 个数中选取元素,使得这些元素之和等于 j 的方案数。;j:不能选 nums[i-1],dp[i][j] = dp[i-1][j];
j>nums[i-1]
:dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];
dp[0][0] = 1;
(装满容量为0的背包,有1种方法,就是装0件物品。)时间复杂度: O ( n × ( sum − target ) ) O(n \times (\textit{sum}-\textit{target})) O(n×(sum−target))
空间复杂度: O ( n × ( sum − target ) ) O(n \times (\textit{sum}-\textit{target})) O(n×(sum−target))
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = 0, n = nums.size();
for(int num : nums) sum += num;
if(sum + target < 0) return 0;
if((sum + target) % 2 !=0) return 0;
int positive = (sum + target) / 2;
vector<vector<int>> dp(n+1, vector<int>(positive+1, 0));
dp[0][0] = 1;
for(int i = 1; i <= n; ++i){
for(int j = 0; j <= positive; ++j){
dp[i][j] = dp[i-1][j]; // 选nums[i-1]
if(j >= nums[i-1]){
dp[i][j] += dp[i-1][j-nums[i-1]]; // 不选nums[i-1]
}
}
}
return dp[n][positive];
}
};
01背包 组合问题:装满容量为 left 的背包,有几种方法。
dp[j] += dp[j - nums[i]];
dp[j] = 0;
dp[0] = 1;
(装满容量为0的背包,有1种方法,就是装0件物品。)输入:nums: [1, 1, 1, 1, 1], target = 3
left = (target + sum) / 2 = (3 + 5) / 2 = 4
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = 0;
for(int num : nums) sum += num;
if(abs(target) > sum) return 0;
if((sum + target) % 2) return 0;
int left = (sum + target) / 2; // 目标组合和
vector<int> dp(left+1, 0);
dp[0] = 1;
for(int i = 0; i < nums.size(); ++i){
for(int j = left; j >= nums[i]; --j){ // 倒序遍历
dp[j] += dp[j - nums[i]]; // 组合数量
}
}
return dp[left];
}
};