LeetCode494. 目标和

137 阅读2分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

题目

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5

解释:
-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

一共有5种方法让最终目标和为3。

提示:

  • 数组非空,且长度不会超过 20 。
  • 初始的数组的和不会超过 1000 。
  • 保证返回的最终结果能被 32 位整数存下。

解题思路

  • 01背包问题是选或者不选,但本题是必须选,是选+还是选-。先将本问题转换为01背包问题。 假设所有符号为+的元素和为x,符号为-的元素和的绝对值是y。 我们想要的 S = 正数和 - 负数和 = x - y 而已知x与y的和是数组总和:x + y = sum 可以求出 x = (S + sum) / 2 = target 也就是我们要从nums数组里选出几个数,令其和为target 于是就转化成了求容量为target的01背包问题 =>要装满容量为target的背包,有几种方案
  • 特例判断 如果S大于sum,不可能实现,返回0 如果x不是整数,也就是S + sum不是偶数,不可能实现,返回0
  • dp[j]代表的意义:填满容量为j的背包,有dp[j]种方法。因为填满容量为0的背包有且只有一种方法,所以dp[0] = 1
  • 状态转移:dp[j] = dp[j] + dp[j - num], 当前填满容量为j的包的方法数 = 之前填满容量为j的包的方法数 + 之前填满容量为j - num的包的方法数 也就是当前数num的加入,可以把之前和为j - num的方法数加入进来。
  • 返回dp[-1],也就是dp[target]
function findTargetSumWays(nums: number[], target: number): number {
    const dp = new Array(nums.length + 1);
    for(let i = 0;i < dp.length;i++){
        dp[i] = new Array();
    }

    // 当nums长度为0时,加减组合的值集合为[0]
    dp[0] = [0];

    for(let i = 1;i < dp.length;i++){
        // 枚举dp[i-1]集合中的值
        for(let j = 0;j < dp[i-1].length;j++){
            const v1 = dp[i-1][j] + nums[i-1]; // 相加
            const v2 = dp[i-1][j] - nums[i-1]; // 相减
            dp[i].push(v1, v2);
        }
    }

    return dp[nums.length].filter(num => num === target).length;
};