题目链接: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
文章讲解:代码随想录
视频讲解:动态规划之背包问题,装满背包有多少种方法?| LeetCode:494.目标和_哔哩哔哩_bilibili
题解1:回溯法
思路:使用回溯法遍历每个元素取正数和负数的情况,计算和是否为 target。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var findTargetSumWays = function(nums, target) {
let res = 0;
const backtracking = function (start, sum) {
if (start === nums.length) {
if (sum === target) {
res++;
}
return;
}
backtracking(start + 1, sum + nums[start]);
backtracking(start + 1, sum - nums[start]);
}
backtracking(0, 0);
return res;
};
分析:时间复杂度为 O(2 ^ n),空间复杂度为 O(n)。
题解2:递归法
思路:nums 中对元素取正负号和为 target的方法数就相当于除了最后一个元素的其他元素任意取正负号,最后一个元素取正号和负号的结果数的和。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var findTargetSumWays = function(nums, target) {
const fun = function (nums, target) {
if (nums.length === 1) {
if (nums[0] === 0 && target === 0) {
return 2;
}
return nums[0] === target || nums[0] === -target ? 1 : 0;
}
const newNums = nums.slice(0, nums.length - 1);
return findTargetSumWays(newNums, target + nums[nums.length - 1]) + findTargetSumWays(newNums, target - nums[nums.length - 1]);
};
return fun(nums, target);
};
分析:时间复杂度为 O(2 ^ n),空间复杂度为 O(n)。
题解3:动态规划
思路:将数组元素分为2组,元素和分别为 sum1 和 sum2,元素总和为 sum,有 sum1 + sum2 = sum。按照题意有 |sum1 - sum2| = |target|,假设 sum1 小于 sum2 可以得出 sum1 = (sum - |target|) / 2。假设有一个背包,容量为 sum1,从数组中取出任意元素,刚好装满背包的方法数即为目标答案。本题是一个01背包问题。
动态规划分析:
- dp 数组以及下标的含义:dp[j] 代表从数组中取出任意元素的和为 j 时有几种方法。
- 递推公式:dp[j] += dp[j - nums[i]]。
- dp 数组初始化:dp[0] = 1,其他全为0。
- 遍历顺序:先遍历物品,再倒序遍历背包。
- 打印 dp 数组:以 nums 为 [1,1,1,1,1],target 为3为例,dp 数组为 [1, 5]。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var findTargetSumWays = function(nums, target) {
const sum = nums.reduce((a, b) => a + b);
target = (sum - Math.abs(target)) / 2;
if (target < 0 || target !== Math.floor(target)) {
return 0;
}
const dp = new Array(target + 1).fill(0);
dp[0] = 1;
for (let i = 0; i < nums.length; i++) {
for (let j = target; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[target];
};
分析:令 n 为数组长度,m 为背包容量,则时间复杂度为 O(n * m),空间复杂度为 O(m)。
收获
本题为一种变种的01背包问题,求解的不是背包的最大价值,而是装满背包的方法数,这种问题的递推公式为 dp[j] += dp[j - nums[i]]。