这是我参与更文挑战的第22天,活动详情查看: 更文挑战
目标和(题号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 <= 200 <= nums[i] <= 10000 <= sum(nums[i]) <= 1000-1000 <= target <= 100
链接
解释
这题啊,这题是经典动态规划。
首先看看暴力解法,暴力解法就很简单了,直接一个递归找到所有可能性就完事了,这没啥可说的。
重点是动态规划的思路,笔者是在回家的路上思考的,思考了一路也没有什么好的想法,即使这题就差把动态规划四个字写在题目上了。
看来答案才知道原来这题是得通过一些计算才能动态规划的,笔者的DP一直卡在符号上面,有了符号就不太方便DP了,因为公式一直想不出来。
那要怎样才能把符号去掉呢?这就需要一些计算了。
这里直接引用官方的解释了,笔者也就不二次翻译了👇:
记数组的元素和为
sum,添加 - 号的元素之和为neg,则其余添加+的元素之和为sum−neg,得到的表达式的结果为即
由于数组
nums中的元素都是非负整数,neg也必须是非负整数,所以上式成立的前提是sum−target是非负偶数。若不符合该条件可直接返回0。若上式成立,问题转化成在数组
nums中选取若干元素,使得这些元素之和等于neg,计算选取元素的方案数。我们可以使用动态规划的方法求解。
之后的过程就很简单了,利用动态规划找到dp[i][j],当j为neg时,就是我们想要的答案了。
自己的答案
无
更好的方法(暴力)
暴力法很简单,👇:
var findTargetSumWays = function(nums, target) {
var count = 0
function DFS(sum, index) {
if (index === nums.length) {
if (sum === target) {
count++
}
} else {
DFS(sum + nums[index], index + 1)
DFS(sum - nums[index], index + 1)
}
}
DFS(0, 0)
return count
};
搞一个深度优先搜索的函数,然后递归调用就完事了,没有什么多余的技巧,在每次到结尾的时候判断sum是否等于target,很简单。
更好的方法(动态规划)
DP这里稍微有点复杂,在开始DP前,有两步准备工作,首先,拿到所有的数字和,之后找到neg和diff进行剪枝。
之后根据neg进行DP操作,找到所有的数字和为neg的可能性,就是这题的答案。
var findTargetSumWays = function(nums, target) {
var sum = 0
diff = 0
neg = 0
len = nums.length
dp = null
for (const num of nums) {
sum += num
}
diff = sum - target
// 剪枝
if (diff < 0 || diff % 2 !== 0) return 0
neg = diff / 2
dp = Array.from({length: len + 1}, () => new Array(neg + 1).fill(0))
dp[0][0] = 1
for (let i = 1; i <= len; i++) {
var num = nums[i - 1]
for (let j = 0; j <= neg; j++) {
dp[i][j] = dp[i - 1][j]
if (j >= num) {
dp[i][j] += dp[i - 1][j - num]
}
}
}
return dp[len][neg]
};
剪枝的过程就是这行代码👇:
if (diff < 0 || diff % 2 !== 0) return 0
第一种情况,由于所有的数字都为非负整数,那么就是说如果diff小于0,就代表着target是大于所有数字和的,这种情况下显然不可能存在任何可能性。
第二种情况,由于在解释中得到了一个等式neg = diff / 2,如果diff无法被二整除,那就意味着neg是个小数,由于所有的数字都是整数,显然不可能组成小数,所以这种情况下也是没有任何可能的。
剪枝完成后就是经典DP了,这里不多赘述,如果看不懂的就多看几次,这种程度的DP还是不难理解的。
更好的方法(动态规划->降维)
又是经典的动态规划降维操作,因为每次新的DP结果只和上一次的结果有关,显然只需要存上一次结果就好。
但这里需要注意,由于需要保证dp[i-1][]中的值,所有内层循环需要进行倒序操作👇:
var findTargetSumWays = function(nums, target) {
let sum = 0;
for (const num of nums) {
sum += num;
}
const diff = sum - target;
if (diff < 0 || diff % 2 !== 0) {
return 0;
}
const neg = diff / 2;
const dp = new Array(neg + 1).fill(0);
dp[0] = 1;
for (const num of nums) {
for (let j = neg; j >= num; j--) {
dp[j] += dp[j - num];
}
}
return dp[neg];
};
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇