[前端]_一起刷leetcode 目标和

150 阅读4分钟

这是我参与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 <= 20
  • 0 <= nums[i] <= 1000
  • 0 <= sum(nums[i]) <= 1000
  • -1000 <= target <= 1000

思路

  1. 由于题目中声明了nums[i]的取值范围是从01000,意味着我们拿到的值可能是0,如果是0的时候加减符号并不影响结果,所以我们先把0挑出来,每个0对我们的结果乘以2即可;

  2. 我们先累加计算出整个数组的和sum,判断一下sum是否大于我们要求的目标值target,如果所有元素加起来都不够的话直接返回0即可;

  3. 我们可以求出sum - target的差值是否能被2整除,为什么需要这一步呢,因为我们一开始是加上了全部的元素,如果其中某个元素的符号变为减去,那么我们要减两次这个数,第一次因为刚刚加过它的值所以要减,第二次是因为它符号变了又得再减,所以我们判断sum - target的差值是否能被2整除,如果不能的话直接返回0即可;

  4. 我们判断整个数组的结果sum是否刚好等于我们的目标值,如果是的话返回0的个数的2次方即可;

  5. 如果以上都不是,那么我们可以根据sum - target的差值除2,来得到我们要减去的值diff即可,比如说我们要求 [1, 2, 3]累加为0的结果,那么整体的和就为6,我们要求的是(sum - target) / 2也就是3, 意味着我们只需要找到元素累加和为3的数量,把它们的和标记为负号即可;

  6. 那么如何找到这个数量呢? 我们可以用一个数组dp来记录每个小于等于diff的值的个数,比如我们找到了1,那么下一轮我们只需要再找到2就行了,这时候我们可以遍历大于1小于diff的元素,给它们每个身上加上dp[自身元素 - 1]的数量,告诉它们,我有了当前这个值了,之前你们差的都可以拿这个补上。不过这个遍历需要从大到小进行遍历,避免前面的结果干扰后面的结果。这里看起来有点绕,我举个例子,比如说我们找的差值是10,现在我们拿到的元素是5,那么5-10的每个元素出现的次数,可以让它们分别加上0-5每个元素出现的次数;

    也就是说:dp[6] += dp[1], dp[7] += dp[2] ...,类似于两数之和一样的思路,有了一个数了,另一个数的数量可以加上减去这个数的差值的数量。

  7. 最终我们返回统计出来的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;
};

结果

image.png

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。