目标和
力扣链接:494. 目标和
给你一个非负整数数组 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
解题
这道题可以通过转换,转变为求解01背包问题。
以示例 1 为例,给定一个nums = [1,1,1,1,1], target = 3
,那么其中一种方案是 -1 + 1 + 1 + 1 + 1 = 3
,那我们怎么思考呢?
该方案 -1 + 1 + 1 + 1 + 1 = 3
实际上是取了:
- 正的集合为
A = {1, 1, 1, 1}
- 负的集合为
B = {1}
对于任意符合条件的方案,一定有sum(A) - sum(B) = target
(sum表示集合中所有元素值的和),这是显而易见的结论。
我们来处理一下这个等式,把左右两边都加上sum(A) + sum(B)
,那么就会变成如下:
sum(A) - sum(B) + sum(A) + sum(B) = target + sum(A) + sum(B)
也就是:
2 * sum(A) = target + sum(nums)
最终化简可得:
sum(A) = (target + sum(nums)) / 2
也就是说,该问题可以转化为一个01背包问题:
- 找到
nums
的一个子集P
,使得sum(P) = (target + sum(nums)) / 2
,
另外还有一个结论,就是如果所有数的累加和是sum
,并且与target
的奇偶性不一样,那么无论如何进行加减运算,都无法得到target
,可以进行特判。
class Solution {
public static int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int n : nums) {
sum += n;
}
if (sum < target || ((target & 1) ^ (sum & 1)) == 1) {
return 0;
}
return compute(nums, (target + sum) >> 1);
}
public static int compute(int[] nums, int t) {
if (t < 0) {
return 0;
}
int[] dp = new int[t + 1];
dp[0] = 1;
for (int num : nums) {
for (int j = t; j >= num; j--) {
dp[j] += dp[j - num];
}
}
return dp[t];
}
}