「这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战」。
题目
给你一个整数数组 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
思路
背包的题目,不过这里要求的,不是最多能装多少,而是刚好装满的方案数。
首先,nums的数值大于等于0,题意就是要把nums分成2个集合,一个集合的数前面放“+”,和记为sump,另一个集合的数前面放“-”,和记为sumn,把整个数组的和记为sum,那么我们可以列出如下方程
sump + sumn = sum (1)
sump - sumn = target (2)
(1)+(2),可得:
2 * sump = sum + target
(1)-(2),可得:
2 * sumn = sum - target
因为sump >= 0 && sumn >= 0,所以必须符合
-sum <= target <= sum
上述的方程才可能有解。
另外一个比较同意忽略掉的条件是 sum + target 和 sum - target必须都是偶数。
满足上述条件的情况下,我们得到sump后,剩下的任务,就是从nums从挑出若干个数,使得和等于sump,求总共有几种方案。(PS:当然,挑出若干个数,使得和等于sumn,也是一样的)
这样,我们就把这个问题转换到了一个01背包,求刚好装满背包有几种方案的问题了,使用01背包的方案来求解。定义状态二维数组dp,dp[i][j]的含义是,从下标为0~i的数字中挑选,满足和为j的方案数。初始条件是dp[i][0] = 1,因为要让和为0,就是不挑任意一个数。状态转移方程可以考虑2种情况
- nums[i] < j,此时不能选择nums[i],不然就超了,所以dp[i][j] = dp[i-1][j]
- nums[i] >= j,此时可以不选择num[i],方案数为dp[i-1][j],也可以选择num[i],方案数为dp[i-1][j-nums[i]],所以 dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]] 综合起来,状态转移方程如下:
dp[i][j] = dp[i-1][j] nums[i] < j
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]] nums[i] >= j
而我们注意到,dp[i][j]只跟dp[i-1][*]有关,所以状态我们可以压缩成一个一维数组。
Java版本代码
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = Arrays.stream(nums).sum();
if (target > sum || target < -sum || (sum+target)%2==1) {
return 0;
}
int sump = (sum + target) / 2;
int[] dp = new int[sump+1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = sump; j >= 0; j--) {
if (j >= nums[i]) {
dp[j] += dp[j-nums[i]];
}
}
}
return dp[sump];
}
}