494. 目标和
题目来源
题目分析
给定一个非负整数数组
nums
和一个目标数target
,我们需要在数组中任意选取整数,添加符号+
或-
,使得运算结果等于target
。我们要计算所有可能的符号添加方法数。
题目难度
- 难度:中等
题目标签
- 标签:
- 数组
- 动态规划
- 回溯
题目限制
1 <= nums.length <= 20
0 <= nums[i] <= 1000
解题思路
这道题可以转化为经典的 01 背包问题:
-
问题转化:我们可以将数组
nums
分成两部分,分别为符号为+
的部分和符号为-
的部分。设P
是符号为+
的部分的和,S
是数组nums
的和,则符号为-
的部分的和为S - P
。题目要求P - (S - P) = target
,简化为2P = S + target
。 -
约束条件:需要确保
S + target
是非负且为偶数,否则不存在满足条件的P
。 -
动态规划:问题转化为一个 01 背包问题,背包容量为
(S + target) / 2
,我们需要找到和为这个值的所有组合数。
核心算法步骤
-
初始化:
- 定义
dp[i]
为和为i
的方法数。 - 初始化 边界
dp[0] = 1
,表示选择的和为 0 的情况有 1 种(不选任何数)。
- 定义
-
状态转移:
- 对于数组中的每个数
num
,我们有两种选择:- 不选择
num
,则dp[i]
保持不变。 - 选择
num
,则dp[i]
需要加上dp[i - num]
。
- 不选择
- 对于数组中的每个数
-
输出结果:
- 最终返回
dp[(S + target) / 2]
即为符合条件的方案数。
- 最终返回
代码实现
以下是求解目标和问题的 Java 代码:
/**
* 494. 目标和
* @param nums 非负整数数组
* @param target 目标数
* @return 方法数
*/
public int findTargetSumWays(int[] nums, int target) {
int n = nums.length;
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((sum + target) % 2 != 0 || sum < Math.abs(target)) {
return 0;
}
target = (sum + target) / 2;
int[] dp = new int[target + 1];
dp[0] = 1;
for (int num : nums) {
for (int i = target; i >= num; i--) {
dp[i] += dp[i - num];
}
}
return dp[target];
}
代码解读
-
findTargetSumWays
方法:- 首先计算数组的总和
sum
,并检查sum + target
是否为偶数和非负。 - 然后,将问题转化为背包容量为
(sum + target) / 2
的 01 背包问题,使用动态规划计算所有可能的组合数。
- 首先计算数组的总和
-
滚动数组优化:
- 使用一维数组
dp
实现滚动数组优化,减少空间复杂度。
- 使用一维数组
性能分析
- 时间复杂度:
O(n * target)
,其中n
是数组的长度,target
是转换后的背包容量。 - 空间复杂度:
O(target)
,使用滚动数组进行空间优化。
测试用例
你可以使用以下测试用例来验证代码的正确性:
int[] nums1 = {1, 1, 1, 1, 1};
int result1 = findTargetSumWays(nums1, 3);
System.out.println(result1);
// 输出: 5 (共有 5 种方法使得表达式结果为 3)
int[] nums2 = {1, 2, 3, 4, 5};
int result2 = findTargetSumWays(nums2, 3);
System.out.println(result2);
// 输出: 3 (共有 3 种方法使得表达式结果为 3)
扩展讨论
优化写法
- 空间优化:
- 使用一维数组
dp
进行空间优化,将原来的二维数组优化为一维滚动数组。 - 其实选择添加正号或者选择添加负号都是可以的,设
S
是数组nums
的和,设P
是符号为+
的部分的和,设Q
是符号为-
的部分的和,则S = Q + P
。题目要求P - Q = target
,可以简化为2P = S + target
,也可以化简为2Q = S - target
,这样把可以把Q
作为背包的容量,同样可以求出解。所以在target
是正数时P
会更小,反之Q
更小,选择更小的数作为背包的容量可以减少循环的次数,减少时间复杂度,因此,将背包容量m = (S - |target|)/2
更好。
- 使用一维数组
其他实现
- DFS + 记忆化:
- 除了动态规划,还可以使用深度优先搜索 (DFS) 加上记忆化搜索来解决这个问题,但对于大规模数据,效率可能不如动态规划。
总结
通过将问题转化为 01 背包问题,并结合动态规划技术,我们能够高效地解决目标和问题。动态规划的核心在于状态的定义和转移,是解决此类问题的有力工具。