⽬标和
力扣:494. 目标和 - 力扣(LeetCode)
给你一个整数数组 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
提示:
1 <= nums.length <= 20
0 <= nums[i] <= 1000
0 <= sum(nums[i]) <= 1000
-1000 <= target <= 1000
如何转化为01背包问题呢。假设数组总和为sum加法的总和为x,那么减法对应的总和就是sum - x。
所以我们要求的是 x - (sum - x) = S
即:x = (S + sum) / 2
此时问题就转化为,装满容量为x背包,有⼏种⽅法。⼤家看到(S + sum) / 2 应该担⼼计算的过程中向下取整有没有影响。这么担⼼就对了,例如sum 是5,S是2的话其实就是⽆解的,所以:
if((sun + target) % 2 == 1)
return 0;
还有当|S| > sun也是无解的这种情况就不用过多解释了吧所以:
if(target > sun || -target > sun)
return 0;
再回归到01背包问题,为什么是01背包呢?因为每个物品(题⽬中的1)只⽤⼀次!这次和之前遇到的背包问题不⼀样了,之前都是求容量为j的背包,最多能装多少。本题则是装满有⼏种⽅法。其实这就是⼀个组合问题了。
动规五部曲:
1. 确定dp数组以及下标的含义
dp[j] 表示:填满j(包括j)这么⼤容积的包,有dp[i]种⽅法其实也可以使⽤⼆维dp数组来求解本题,dp[i][j]:使⽤ 下标为[0, i]的nums[i]能够凑满j(包括j)这么⼤容量的包,有dp[i][j]种⽅法。下⾯我都是统⼀使⽤⼀维数组进⾏讲解, ⼆维降为⼀维(滚动数组),其实就是上⼀层拷⻉下来,这个我在动态规划进阶02:01背包理论基础(滚动数组) - 掘金 (juejin.cn)也有介绍。
2. 确定递推公式
有哪些来源可以推出dp[j]呢?不考虑nums[i]的情况下,填满容量为j - nums[i]的背包,有dp[j - nums[i]]中⽅法。
那么只要搞到nums[i]的话,凑成dp[j]就有dp[j - nums[i]] 种⽅法。
举⼀个例⼦:填满背包容量为3有dp[3]种⽅法。那么只需要有⼀个nums[i] = 2,有dp[3]⽅法可以凑⻬容量为3的背包,相应的就有多少种⽅法可以凑⻬容量为dp[5]的背包。那么需要把这些⽅法累加起来就可以了,dp[5] += dp[5 - 5]所以求组合类问题的公式,都是类似这种:
dp[j] += dp[j - nums[i]]
3. 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]]推导出来。
4. 确定遍历顺序
对于01背包问题⼀维dp的遍历前面的文章我们都讲过,nums放在外循环,target在内循环,且内循环倒序。
5. 举例推导dp数组
输⼊:nums: [1, 1, 1, 1, 1], S: 3
bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4
dp数组状态变化如下:
如果对结果不理解,回顾一下递推公式和遍历顺序。
以上分析完毕,Java代码如下:
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int l = nums.length;
int sun = 0;
for(int i : nums)
sun += i;
if(target > sun || -target > sun)
return 0;
if((sun + target) % 2 == 1)
return 0;
int t = (sun + target) / 2;
int[] dp = new int[t + 1];
dp[0] = 1;
for(int i = 0; i < l; i++){
for(int j = t; j >= nums[i]; j--){
dp[j] += dp[j - nums[i]];
}
}
return dp[t];
}
}