LeetCode—494. 目标和[Target Sum]——分析及代码[Java]
一、题目
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-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
一共有5种方法让最终目标和为3。
提示:
- 数组非空,且长度不会超过 20 。
- 初始的数组的和不会超过 1000 。
- 保证返回的最终结果能被 32 位整数存下。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二、分析及代码
1. 动态规划
(1)思路
设 dp[i][j] 表示数组中前 i 个元素组成和为 j 的方案数,则对第 i+1 个元素
dp[i + 1][j - nums[i + 1]] += dp[i][j]
dp[i + 1][j + nums[i + 1]] += dp[i][j]
最终,dp[n][S] 就是所求解。
由于初始的数组的和不会超过 1000 ,即数组正负和范围在 [-1000,1000] 范围内,可将下标 j 整体加 1000,用 [0,2000] 表示 [-1000,1000] 的元素和情况。
(2)代码
class Solution {
public int findTargetSumWays(int[] nums, int S) {
if (nums.length == 0 || S > 1000 || S < -1000) // S 可能超出 [-1000,1000]
return 0;
int [][] dp = new int[nums.length + 1][2001];//[-1000,1000] 共有 2001 个数
dp[0][1000] += 1;
for (int i = 1; i <= nums.length; i++) {
for (int j = 0; j < 2001; j++) {
if (dp[i - 1][j] != 0) {
dp[i][j + nums[i - 1]] += dp[i - 1][j];
dp[i][j - nums[i - 1]] += dp[i - 1][j];
}
}
}
return dp[nums.length][1000 + S];
}
}
(3)结果
执行用时 :10 ms, 在所有 Java 提交中击败了 68.57% 的用户;
内存消耗 :38.2 MB, 在所有 Java 提交中击败了 37.91% 的用户。
2. 动态规划+节省空间
(1)思路
因为 dp[i+1] 只和 dp[i] 有关,用两个一维数组迭代即可实现。
(2)代码
class Solution {
public int findTargetSumWays(int[] nums, int S) {
if (nums.length == 0 || S > 1000 || S < -1000) // S 可能超出 [-1000,1000]
return 0;
int [] dp1 = new int[2001];//[-1000,1000] 共有 2001 个数
int [] dp2 = new int[2001];
dp1[1000] += 1;
for (int i = 0; i < nums.length; i++) {
if ( (i & 1) == 1) {
for (int j = 0; j < 2001; j++) {
if (dp2[j] != 0) {
dp1[j + nums[i]] += dp2[j];
dp1[j - nums[i]] += dp2[j];
dp2[j] = 0;
}
}
} else {
for (int j = 0; j < 2001; j++) {
if (dp1[j] != 0) {
dp2[j + nums[i]] += dp1[j];
dp2[j - nums[i]] += dp1[j];
dp1[j] = 0;
}
}
}
}
return (nums.length & 1) == 1 ? dp2[1000 + S] : dp1[1000 + S];
}
}
(3)结果
执行用时 :6 ms, 在所有 Java 提交中击败了 76.04% 的用户;
内存消耗 :37.7 MB, 在所有 Java 提交中击败了 39.46% 的用户。
3. 动态规划+节省空间+预判
(1)思路
在开始动态规划前,可通过计算所给数组和与 S 的关系,提前排除不可能实现的情况。
(2)代码
class Solution {
public int findTargetSumWays(int[] nums, int S) {
if (nums.length == 0 || S > 1000 || S < -1000) // S 可能超出 [-1000,1000]
return 0;
int sum = 0;
for (int num : nums)
sum += num;
if (S < -sum || S > sum || ((S + sum) & 1) == 1)
return 0;
int len = 2 * sum + 1;
int [] dp1 = new int[len];
int [] dp2 = new int[len];
dp1[sum] += 1;
for (int i = 0; i < nums.length; i++) {
if ( (i & 1) == 1) {
for (int j = 0; j < len; j++) {
if (dp2[j] != 0) {
dp1[j + nums[i]] += dp2[j];
dp1[j - nums[i]] += dp2[j];
dp2[j] = 0;
}
}
} else {
for (int j = 0; j < len; j++) {
if (dp1[j] != 0) {
dp2[j + nums[i]] += dp1[j];
dp2[j - nums[i]] += dp1[j];
dp1[j] = 0;
}
}
}
}
return (nums.length & 1) == 1 ? dp2[sum + S] : dp1[sum + S];
}
}
(3)结果
执行用时 :4 ms, 在所有 Java 提交中击败了 82.14% 的用户;
内存消耗 :38 MB, 在所有 Java 提交中击败了 38.88% 的用户。
三、其他
暂无。