题目:
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1"
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
------------------
示例:
输入: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
提示:
1 <= nums.length <= 20
0 <= nums[i] <= 1000
0 <= sum(nums[i]) <= 1000
-1000 <= target <= 1000
-----------------
思考:
本题要如何使表达式结果为target,
既然为target,那么就一定有 left组合 - right组合 = target。
left + right等于sum,而sum是固定的。
公式来了, left - (sum - left) = target -> left = (target + sum)/2 。
target是固定的,sum是固定的,left就可以求出来。
此时问题就是在集合nums中找出和为left的组合。
如何转化为01背包问题呢。
假设加法的总和为x,那么减法对应的总和就是sum - x。
所以我们要求的是 x - (sum - x) = S
x = (S + sum) / 2
此时问题就转化为,装满容量为x背包,有几种方法。
大家看到(S + sum) / 2 应该担心计算的过程中向下取整有没有影响。
这么担心就对了,例如sum 是5,S是2的话其实就是无解的,所以:
if ((S + sum) % 2 == 1) return 0;
同时如果 S的绝对值已经大于sum,那么也是没有方案的。
if (abs(S) > sum) return 0;
再回归到01背包问题,为什么是01背包呢?
因为每个物品(题目中的1)只用一次!
这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。
本题则是装满有几种方法。其实这就是一个组合问题了。
确定dp数组以及下标的含义
dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
确定递推公式
有哪些来源可以推出dp[j]呢?
填满容量为j - nums[i]的背包,有dp[j - nums[i]]种方法。
那么只要搞到nums[i]的话,凑成dp[j]就有dp[j - nums[i]] 种方法。
举一个例子,nums[i] = 2: dp[3],填满背包容量为3的话,有dp[3]种方法。
那么只需要搞到一个2(nums[i]),有dp[3]方法可以凑齐容量为3的背包,
相应的就有多少种方法可以凑齐容量为5的背包。
那么需要把 这些方法累加起来就可以了,dp[j] += dp[j - nums[i]]
所以求组合类问题的公式,都是类似这种:
dp[j] += dp[j - nums[i]]
这个公式在后面在讲解背包解决排列组合问题的时候还会用到!
dp数组如何初始化
从递归公式可以看出,在初始化的时候dp[0] 一定要初始化为1,
因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递归结果将都是0。
dp[0] = 1,理论上也很好解释,装满容量为0的背包,有1种方法,就是装0件物品。
dp[j]其他下标对应的数值应该初始化为0,从递归公式也可以看出,dp[j]要保证是0的初始值,
才能正确的由dp[j - nums[i]]推导出来。
确定遍历顺序
01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。
举例推导dp数组
输入:nums: [1, 1, 1, 1, 1], S: 3
bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4
dp数组状态变化如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/11ece51f5198c94a3dbfcb894852e505.png)
本题还是有点难度,大家也可以记住,在求装满背包有几种方法的情况下,递推公式一般为:
dp[j] += dp[j - nums[i]];
后面我们在讲解完全背包的时候,还会用到这个递推公式!
----------------
代码:
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) sum += num;
if ((sum + target) % 2 != 0) return 0;
int size = (target + sum) / 2;
if (size < 0) size = -size;
int[] dp = new int[size + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = size; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[size];
}
}
LC