这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战
题目
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
提示:
1 <= nums.length <= 200 <= nums[i] <= 10000 <= sum(nums[i]) <= 1000-1000 <= target <= 1000
思路
-
由于题目中声明了
nums[i]的取值范围是从0到1000,意味着我们拿到的值可能是0,如果是0的时候加减符号并不影响结果,所以我们先把0挑出来,每个0对我们的结果乘以2即可; -
我们先累加计算出整个数组的和
sum,判断一下sum是否大于我们要求的目标值target,如果所有元素加起来都不够的话直接返回0即可; -
我们可以求出
sum - target的差值是否能被2整除,为什么需要这一步呢,因为我们一开始是加上了全部的元素,如果其中某个元素的符号变为减去,那么我们要减两次这个数,第一次因为刚刚加过它的值所以要减,第二次是因为它符号变了又得再减,所以我们判断sum - target的差值是否能被2整除,如果不能的话直接返回0即可; -
我们判断整个数组的结果
sum是否刚好等于我们的目标值,如果是的话返回0的个数的2次方即可; -
如果以上都不是,那么我们可以根据
sum - target的差值除2,来得到我们要减去的值diff即可,比如说我们要求[1, 2, 3]累加为0的结果,那么整体的和就为6,我们要求的是(sum - target) / 2也就是3, 意味着我们只需要找到元素累加和为3的数量,把它们的和标记为负号即可; -
那么如何找到这个数量呢? 我们可以用一个数组
dp来记录每个小于等于diff的值的个数,比如我们找到了1,那么下一轮我们只需要再找到2就行了,这时候我们可以遍历大于1小于diff的元素,给它们每个身上加上dp[自身元素 - 1]的数量,告诉它们,我有了当前这个值了,之前你们差的都可以拿这个补上。不过这个遍历需要从大到小进行遍历,避免前面的结果干扰后面的结果。这里看起来有点绕,我举个例子,比如说我们找的差值是10,现在我们拿到的元素是5,那么5-10的每个元素出现的次数,可以让它们分别加上0-5每个元素出现的次数;也就是说:
dp[6] += dp[1], dp[7] += dp[2] ...,类似于两数之和一样的思路,有了一个数了,另一个数的数量可以加上减去这个数的差值的数量。 -
最终我们返回统计出来的
dp[diff]的数量*0的个数的二次方即可。
实现
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var findTargetSumWays = function(nums, target) {
const n = nums.length;
nums = nums.filter(v => v);
// 先判断和是否大于目标值,并且是否能被满减
const sum = nums.length ? nums.reduce((total, cur) => total += cur) : 0;
// 为什么除以2, 因为本身求和的时候加上了这个数, 如果不加上还要减掉就得减去这个数的2倍
if (sum < target || (sum - target) % 2 === 1) return 0;
// 遇到0答案就得乘以2,先统计有多少个0
const base = Math.pow(2, n - nums.length);
if (sum === target) return base;
// 有多出来的数,需要减掉
const diff = (sum - target) / 2;
// 枚举每个数字出现的数量
const dp = new Array(diff + 1).fill(0);
// 一开始所有元素都不选的时候和为0
dp[0] = 1;
// 枚举每一个不大于目标值的数,然后他跟目标值中间的所有项都加上有了这个值的变化
for (let i = 0; i < nums.length; i++) {
// 遍历中间可能因为这个值产生变化的片段,加上有了这个值后差值的数量
// 记得这里需要从大到小的遍历,不然前面的结果可能会干扰后面的结果
for (let j = diff; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[diff] * base;
};
结果
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。