题目描述:
给你一个非负整数数组
nums和一个整数target。向数组中的每个整数前添加
'+'或'-',然后串联起所有整数,可以构造一个 表达式 :
- 例如,
nums = [2, 1],可以在2之前添加'+',在1之前添加'-',然后串联起来得到表达式"+2-1"。返回可以通过上述方法构造的、运算结果等于
target的不同 表达式 的数目。示例 1:
输入: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示例 2:
输入:nums = [1], target = 1 输出:1
解题思路:
方法一:回溯(DFS)
思路:
对于每个数字,有两种选择:加 + 或加 -。
-
递归到最后一个数字时,判断和是否等于
target -
是则计数 +1
代码实现:
cpp
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int count = 0;
backtrack(nums, target, 0, 0, count);
return count;
}
private:
void backtrack(vector<int>& nums, int target, int index, int sum, int& count) {
if (index == nums.size()) {
if (sum == target) count++;
return;
}
// 选择 +
backtrack(nums, target, index + 1, sum + nums[index], count);
// 选择 -
backtrack(nums, target, index + 1, sum - nums[index], count);
}
};
复杂度分析:
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(2^n) | 每个数字两种选择 |
| 空间复杂度 | O(n) | 递归栈深度 |
缺点:数据量大时超时。
方法二:动态规划(转化为 01 背包)
核心转化:
设所有数字的和为 sum,加 + 的数字和为 P,加 - 的数字和为 N。
P + N = sum
P - N = target
两式相加:2P = sum + target,即:
P = (sum + target) / 2
问题转化为 :从 nums 中选一些数字,使它们的和等于 P,求方案数。
这就是 01 背包问题!
代码实现:
cpp
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = 0;
for (int num : nums) sum += num;
// 无法整除,或目标绝对值超过总和
if ((sum + target) % 2 != 0 || abs(target) > sum) return 0;
int P = (sum + target) / 2;
// dp[j] = 和为 j 的方案数
vector<int> dp(P + 1, 0);
dp[0] = 1;
for (int num : nums) {
for (int j = P; j >= num; j--) {
dp[j] += dp[j - num];
}
}
return dp[P];
}
};
具体过程示例:
nums = [1,1,1,1,1], target = 3
sum = 5
P = (5 + 3) / 2 = 4
问题转化为:从 [1,1,1,1,1] 中选数字,和为 4 的方案数
dp[0] = 1
遍历每个 1:
dp[1] += dp[0] → dp[1] = 1
dp[2] += dp[1] → dp[2] = 1
dp[3] += dp[2] → dp[3] = 1
dp[4] += dp[3] → dp[4] = 1
遍历第二个 1:
dp[1] += dp[0] → dp[1] = 2
dp[2] += dp[1] → dp[2] = 3
dp[3] += dp[2] → dp[3] = 4
dp[4] += dp[3] → dp[4] = 5
...最终 dp[4] = 5 ✅
复杂度分析:
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n × P) | n 个数字,P 是目标和 |
| 空间复杂度 | O(P) | dp 数组 |
方法三:记忆化搜索
思路:
在回溯的基础上,用哈希表记录 (index, sum) 的结果,避免重复计算。
代码实现:
cpp
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
unordered_map<string, int> memo;
return dfs(nums, target, 0, 0, memo);
}
private:
int dfs(vector<int>& nums, int target, int index, int sum,
unordered_map<string, int>& memo) {
if (index == nums.size()) {
return sum == target ? 1 : 0;
}
string key = to_string(index) + "," + to_string(sum);
if (memo.count(key)) return memo[key];
int count = dfs(nums, target, index + 1, sum + nums[index], memo)
+ dfs(nums, target, index + 1, sum - nums[index], memo);
memo[key] = count;
return count;
}
};
复杂度分析:
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n × sum) | 每个 (index, sum) 只算一次 |
| 空间复杂度 | O(n × sum) | 哈希表 |
三种方法对比:
| 方法 | 时间复杂度 | 空间复杂度 | 推荐度 |
|---|---|---|---|
| 回溯 | O(2^n) | O(n) | ⭐⭐ |
| 动态规划(01背包) | O(n × P) | O(P) | ⭐⭐⭐⭐⭐ |
| 记忆化搜索 | O(n × sum) | O(n × sum) | ⭐⭐⭐⭐ |
总结:
| 要点 | 说明 |
|---|---|
| 核心思想 | 转化为 01 背包:选一些数字和为 P |
| 关键公式 | P = (sum + target) / 2 |
| 时间复杂度 | O(n × P) |
| 空间复杂度 | O(P) |