目标和 leetcode (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
思路 (动态规划)
思路1
dp 数组为二维,dp[i][j] 表示前i个数组合生成 j 时所有可能的种数。
前 i 个 数构成 j 可以看作 :前 i - 1 个数构成(j - nums[i]) 加上 nums[i] 或 前 i - 1 个数构成(j + nums[i]) 减去 nums[i]
所以状态转移方程 dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]
nums[i] 的取值范围是 [0,1000],target 的取值范围是 [-1000,1000]
当nums[i] = 1000时,dp[i][-1000]可以由 dp[i-1][-2000] 和 dp[i-1][0] 构成
dp[i][1000] 可以由 dp[i-1][0] 和 dp[i-1][2000] 构成。
所以 j 的范围在 -2000 到 2000
为了防止数组越界,令 int[][] dp = new int[nums.length][4001]
代码1
class Solution {
// 0,1 背包是取和不取 这道题是 取正和取负
public int findTargetSumWays(int[] nums, int target) {
// dp的第二维 取值范围为[-2000,2000]跨度为4000
int[][] dp = new int[nums.length][4001]; // dp[i][j] 表示前i个物品组合成j+2000的可能性的种数
// 状态转移方程感觉是个递归 dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j+nums[i]]
dp[0][nums[0] + 2000] += 1;
dp[0][-nums[0] + 2000] += 1; //因为如果是0 的话 取正取负 和为0就有两种
for (int i = 1; i < nums.length; i++) {
for (int j = nums[i]; j<=4000-nums[i]; j++) {
//这里要注意判别,防止越界
dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]];
}
}
return dp[nums.length - 1][target + 2000];
}
}
注意::代码中第8-9行,使用 += 而不是 =,因为当nums[i] =0时取正还是负都是0,但是算两种方法
思路2
对于每个数,我们有两种选择。(1)让它的符号为正 (2)让它的符号为负
所以,可以根据我们的选择将它们分为两堆。
设选择为正的这一堆数字的和为pos,设选择为负的这一堆数字和为neg。
所有数字的总和为 sum,目标为 target。
我们有以下表达式:
{ n e g + p o s = s u m ( 1 ) p o s − n e g = t a r g e t ( 2 ) \left\{ \begin{aligned} neg +pos & = sum &(1)\\ pos - neg & = target &(2) \\ \end{aligned} \right. {
neg+pospos−neg=sum=target(1)(2)
用(1)+(2)得 2 ∗ p o s = s u m + t a r g e t 2 *pos = sum + target 2∗pos=sum+target
所以我们可以把该问题转化为从一堆数中取任意个,使它们的和为 ( s u m + t a r g e t ) / 2 (sum + target)/2 (sum+target)/2。总共有多少种取法?
代码2
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((target + sum) % 2 != 0) return 0; // target + sum 不能被 2 整除,表示无法选数构成pos
int pos = Math.abs((sum + target) / 2);
int[] dp = new int[pos + 1]; // dp[i]表示 从中 nums中取任意个数,能构成 i 的种数
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = pos; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[pos];
}
}