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
思路:(动态规划)
该问题可以转换为 Subset Sum 问题,从而使用 0-1背包 的方法来求解。
可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
s
u
m
(
P
)
−
s
u
m
(
N
)
=
t
a
r
g
e
t
sum(P) - sum(N) = target
sum(P)−sum(N)=target
两边同时同时加
s
u
m
(
P
)
+
s
u
m
(
N
)
sum(P) + sum(N)
sum(P)+sum(N) :
s
u
m
(
P
)
+
s
u
m
(
P
)
+
s
u
m
(
N
)
−
s
u
m
(
N
)
=
t
a
r
g
e
t
+
s
u
m
(
P
)
+
s
u
m
(
N
)
sum(P) + sum(P) + sum(N) - sum(N) = target + sum(P) + sum(N)
sum(P)+sum(P)+sum(N)−sum(N)=target+sum(P)+sum(N)
从而得:
2
∗
s
u
m
(
P
)
=
t
a
r
g
e
t
+
s
u
m
(
n
u
m
s
)
2 * sum(P) = target + sum(nums)
2∗sum(P)=target+sum(nums)
即:
s
u
m
(
P
)
=
(
t
a
r
g
e
t
+
s
u
m
(
n
u
m
s
)
)
/
2
sum(P) = (target + sum(nums)) / 2
sum(P)=(target+sum(nums))/2
定义一维数组 dp: dp[j] 表示 共有dp[j] 种方法让目标和为 j ,
- 边界条件dp[0] = 1;
- 一维dp,需要按倒序循环求解,状态转移方程为:
d p [ j ] + = d p [ j − n u m s [ i ] ] dp[j] += dp[j - nums[i]] dp[j]+=dp[j−nums[i]] - 注意特殊情况的处理。
代码:(Java)
0 - 1 背包:
public class FindTargetSumWays {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] nums = {1,1,1,1,1};
int target = 3;
System.out.println(findTargetSumWays(nums, target));
}
public static int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for(int i = 0; i < nums.length; i++) {
sum += nums[i];
}
if(target > sum || target < -sum || (sum + target) % 2 != 0)
return 0;
int w = (sum + target) / 2;
int[] dp = new int[w + 1];
dp[0] = 1;
for(int i = 0; i < nums.length; i++) {
for(int j = w; j >= nums[i]; j --) {
dp[j] += dp[j - nums[i]];
}
}
return sum;
}
}
DFS解法:
public class FindTargetSumWays {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] nums = {1,1,1,1,1};
int target = 3;
System.out.println(findTargetSumWays(nums, target));
}
public static int findTargetSumWays(int[] nums, int target) {
return findTargetSumWays(nums, 0, target);
}
private static int findTargetSumWays(int[] nums, int start, int target) {
// TODO Auto-generated method stub
if(start == nums.length) {
return target == 0 ? 1 : 0 ;
}
return findTargetSumWays(nums, start + 1, target + nums[start]) + findTargetSumWays(nums, start + 1, target - nums[start]);
}
}
运行结果:
复杂度分析:(0-1背包)
- 时间复杂度: O ( n × ( s u m − t a r g e t ) ) O(n×(sum−target)) O(n×(sum−target)),其中 n 是数组 nums 的长度,sum 是数组 nums 的元素和,target是目标数。动态规划有 ( n + 1 ) × ( sum − target 2 + 1 ) (n+1) \times (\dfrac{\textit{sum}-\textit{target}}{2}+1) (n+1)×(2sum−target+1) 个状态,需要计算每个状态的值。
- 空间复杂度: O ( s u m − t a r g e t ) O(sum−target) O(sum−target),其中 sum 是数组 nums 的元素和,target 是目标数。使用空间优化的实现,需要创建长度为 sum − target 2 + 1 \dfrac{\textit{sum}-\textit{target}}{2}+1 2sum−target+1 的数组 dp .
注:仅供学习参考!
题目来源:力扣。