一、题目描述
给你一个整数数组 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
二、解题思路
分析题目:首先假设记数组的元素总和为sum,那么前面天剑 “-” 号之和为neg,则剩余元素添加 “+” 之和为sum-neg,得到我们要求的target的表达式就是:target=(sum-neg)-neg
就有neg = (sum-target)/2
根据式子我们需要注意几个点,
- target必须是小于sum的,如果有target>sum,则返回0;
- 因为nums是非负整数,所以前面添加 “-” 的元素之neg也应该是非负整数,所以sum-target应该是非负偶数
代码如下:
if(target>sum || (sum-target)%2!=0){
return 0;
}
通过上面分析,怎么代入到01背包问题呢?
首先是nums数组中额每个元素都只用一次(相当于背包中的物品),另外是从nums数组中选取若干元素。不同的是背包问题都是求容量为j的背包最多能装多少。本题则是装满有几种方法。其实是一个组合问题。
问题转化为:在数组nums[i]中选取若干元素,是的这些元素之和等于neg,计算选取元素的方案数,动态规划。
动规五部曲:
第一步:确定dp数组以及下标的含义
dp[j]
表示:填满j(包括j)这么⼤容积的包,有dp[i]
种⽅法其实也可以使⽤⼆维dp数组来求解本题,dp[i][j]
:使⽤ 下标为[0, i]的nums[i]能够凑满j(包括j)这么⼤容量的包,有dp[i][j]
种⽅法
第二步:确定递推公式
有哪些来源可以推出dp[j]呢?
当前填满容量为j的包的方法数 = 之前填满容量为j的包的方法数 + 之前填满容量为j - num[i]的包的方法数
也就是考虑选不选nums[i],
- 当不选的时候:之前填满容量为j的包的方法数(
dp[j] = dp[j]
) - 当选的时候:之前填满容量为j - num[i]的包的方法数(
dp[j] = dp[j-nums[i]]
)
由于求的是可能的总的方案数,所以当前填满容量为j的包的方法数:
dp[j] = dp[j] + dp[j - num[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]]推导出来。
第四步:确定遍历顺序
for(int i=0; i<nums.length; i++){
for(int j=neg; j>=nums[i]; j--){
dp[j] += dp[j-nums[i]];
}
}
第五步:举例推导dp数组
输⼊:nums: [1, 1, 1, 1, 1],target: 3
neg = (sum - target) / 2 = (3 + 5) / 2 = 4
dp数组状态变化如下:
三、代码演示
class Solution {
public int findTargetSumWays(int[] nums, int target) {
//存储数组中所有数的总和
int sum = 0;
//遍历数组,求解总和
for(int i=0; i<nums.length; i++){
sum += nums[i];
}
//特判
if(target>sum || (sum-target)%2!=0){
return 0;
}
//声明dp
int neg = (sum-target)/2;
int[] dp = new int[neg+1];
//dp初始化
dp[0] = 1;
//遍历背包
for(int i=0; i<nums.length; i++){
for(int j=neg; j>=nums[i]; j--){
dp[j] += dp[j-nums[i]];
}
}
return dp[neg];
}
}