494. 目标和

keybot
1
2025-08-20

#medium #回溯
给你一个非负整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
提示:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 1000
  • 0 <= sum(nums[i]) <= 1000
  • -1000 <= target <= 1000

思路
数组 nums 的每个元素都可以添加符号 +-,因此每个元素有 2 种添加符号的方法,n 个数共有 $2^N$ 种添加符号的方法,对应 $2 ^N$ 种不同的表达式。当 n 个元素都添加符号之后,即得到一种表达式,如果表达式的结果等于目标数 target,则该表达式即为符合要求的表达式。

可以使用回溯的方法遍历所有的表达式,回溯过程中维护一个计数器 count,当遇到一种表达式的结果等于目标数 target 时,将 count 的值加 1。遍历完所有的表达式之后,即可得到结果等于目标数 target 的表达式的数目。

int count;
void backtrack(int* nums, int numSize, int target, int index, int sum) {
    if (index == numSize) {
        if (sum == target)
            count++;
    } else {
        backtrack(nums, numSize, target, index + 1, sum + nums[index]);
        backtrack(nums, numSize, target, index + 1, sum - nums[index]);
    }
}
int findTargetSumWays(int* nums, int numsSize, int target) {
    count = 0;
    backtrack(nums, numsSize, target, 0, 0);
    return count;
}

记数组的元素和为 sum,添加 - 号的元素之和为 neg,则其余添加 + 的元素之和为 sum−neg,得到的表达式的结果为

$$(sum−neg)−neg=sum−2⋅neg=target$$

$$Neg= \frac{Sum−target}{2}$$
由于数组 nums 中的元素都是非负整数,neg 也必须是非负整数,所以上式成立的前提是 sum−target 是非负偶数。若不符合该条件可直接返回 0。

若上式成立,问题转化成在数组 nums 中选取若干元素,使得这些元素之和等于 neg,计算选取元素的方案数。我们可以使用动态规划的方法求解。

定义二维数组 dp,其中 dp[i][j] 表示在数组 nums 的前 i 个数中选取元素,使得这些元素之和等于 j 的方案数。假设数组 nums 的长度为 n,则最终答案为 dp[n][neg]。

当没有任何元素可以选取时,元素和只能是 0,对应的方案数是 1,因此动态规划的边界条件是:
$$
dp[0][j] =
\begin{cases}
1, & \text j = 0 \
0, & \text j \geq 0
\end{cases}

$$
当 $\text 1 \leq i\leq n$ 时,对于数组 nums 中的第 i 个元素 num(i 的计数从 1 开始),遍历 $\text 0 \leq j\leq neg$ \,计算 dp[i][j] 的值:

如果 j<num,则不能选 num,此时有 dp[i][j]=dp[i−1][j];

如果 j≥num,则如果不选 num,方案数是 dp[i−1][j],如果选 num,方案数是 dp[i−1][j−num],此时有 dp[i][j]=dp[i−1][j]+dp[i−1][j−num]。

因此状态转移方程如下:

Dp[i][j]={
Dp[i−1][j],
Dp[i−1][j]+dp[i−1][j−nums[i]],

J<nums[i]
J≥nums[i]

最终得到 dp[n][neg] 的值即为答案。

由此可以得到空间复杂度为 O (n×neg) 的实现。