[动态规划] 494. 目标和

150 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

每日刷题 2022.06.19

题目

  • 给你一个整数数组 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

解题思路

  • 将问题转换为01背包进行求解
  • 什么是01背包,每个物品都只能选择一次(两种可能性:选择 or 不选),如何才能达到背包的容量。
  • 因为优化后,就使用一维数组来进行求解。
  • 需要特别注意的:负数的情况,数组就没有办法进行求解,会报错。当然你可以选择使用其他的数据结构,例如map,但是同样也会出现问题,你循环的变量需要从几开始呢?如果从1开始,那么你结束位置是个负数,如何处理?
  • 因此直接将负数取绝对值转换成整数就可以了,因为我们要求得是背包的重量,因此在含义上来看,就可以直接取绝对值。
  • 将操作数连接想象成二叉树,用 0 做根节点,数组中第一个元素的正负值分别为根节点的左右子树,数组中的后面元素的节点的正负值均为前一个元素正负节点的左右节点。 问题就变成了 有几条从根节点到叶子节点的元素值等于目标值的路径。即为满足条件的 DFS 路径条数。

AC代码

var findTargetSumWays = function(nums, target) {
  // 根据分析可知:可以将其分为正子集和负子集
  // 根据运算分析可知:sum(P) * 2 = target + sum(nums)
  // 需要知道有多少种能够组成sum(P)的做法就行
  const n = nums.length;
  let sum = 0;
  for (let i = 0; i < n; i++) {
    sum += nums[i];
  }
  if((target + sum) & 1 == 1 || sum < target){
    // 奇数
    return 0;
  }
  // 偶数执行下面的做法
  let half = (target + sum) / 2;
  // 需要查找能够凑够sum[p]的情况
  // 又转换为01背包,能够组成容量为nums(P)的方式有多少种?
  half = half < 0 ? -half : half;
  let dp = new Array(half + 1).fill(0);
  // 尽管总数为0,但是从0到0也是一种情况
  dp[0] = 1;
  for(let i = 0; i < n; i++) {
    for(let j = half; j >= nums[i]; j--) {
      dp[j] += dp[j - nums[i]];
    }
  }
  return dp[half];
};