题目链接:目标和
题目描述:
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
分析:数组中的每一个数字都要用到,而且每一个数字都有两种情况,+或者-。
方法一:回溯(递归,dfs)O(2^n)
代码如下:
var findTargetSumWays = function (nums, target) {
let count = 0;
const backtrack = function (nums, target, index, sum) {
if (index === nums.length) {
if (sum === target)
count++;
} else {
// 加
backtrack(nums, target, index + 1, sum + nums[index]);
// 减
backtrack(nums, target, index + 1, sum - nums[index]);
}
}
backtrack(nums, target, 0, 0);
return count;
};
方法二:记忆化搜索
在上述回溯(递归)的过程中,对于相同的<index,sum>,之后的递归过程都是一样的,所以就可以把某一个<index,sum>对应的结果存储起来,之后再遍历到的时候直接返回值即可。
使用map进行存储,key为字符串index+"_"+sum,value是对应的方案数。
即将过程中得到的结果记录下来,再次需要的时候直接使用。就能够减少一些递归过程,提升性能。
代码如下:
var findTargetSumWays = function (nums, target) {
const map = new Map();
const backtrack = function (nums, target, index, sum) {
let key = index + "_" + sum;
if (map.has(key))
return map.get(key);
if (index === nums.length) {
map.set(key, sum === target ? 1 : 0)
return map.get(key);
}
// 加
let add = backtrack(nums, target, index + 1, sum + nums[index]);
// 减
let sub = backtrack(nums, target, index + 1, sum - nums[index]);
map.set(key, add + sub);
return map.get(key);
}
return backtrack(nums, target, 0, 0);
};
方法三:动态规划
数组中所有元素的和为sum,为了构成target,符号为-的元素的和为diff,那么符号为+的元素的和为sum-diff,由题意可得,(sum-diff)-diff=sum-2*diff=target,即diff=(sum-target)/2。
那么求构成target的方案数,显然就等价于求几个元素的和为diff的方案数。
问题转换成了,从n个数中选择若干个元素,使其和为diff的方案数。
可以使用动态规划,dp[i][j]表示从nums的前i-1个数中选取若干个数,使其和为j的方案数。
边界条件是:dp[0][0]=1,
最终目标为:dp[n][diff]
状态转移方程为:
1)若sum-target是负数或奇数,那么直接返回0即可。
2)令nums[i-1]为num,
a。若num>j,那么num当然是不可取的,此时dp[i][j]=dp[i-1][j]
b。若num<=j,那么num可取可不取,即dp[i][j]=dp[i-1][j]+dp[i-1][j-num]
代码如下:
var findTargetSumWays = function (nums, target) {
const length = nums.length;
let sum = 0;
for (const num of nums)
sum += num;
if (sum < target || (sum - target) % 2 === 1)
return 0;
const diff = (sum - target) / 2;
// 二维数组初始化
const dp = new Array(length + 1).fill(0).map(() => new Array(diff + 1).fill(0));
dp[0][0] = 1;
for (let i = 1; i <= length; i++) {
let num = nums[i - 1];
for (let j = 0; j <= diff; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= num)
dp[i][j] += dp[i - 1][j - num];
}
}
return dp[length][diff];
};