题目
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
提示:
1 <= nums.length <= 20
0 <= nums[i] <= 1000
0 <= sum(nums[i]) <= 1000
-1000 <= target <= 100
题解
1、回溯
数组nums的每个元素都可以添加符号+或-,因此每个元素都有两种选择,n个元素就有
2
n
2^n
2n种选择。提示里数组元素个数不超过20,因此可以使用回溯法,把每个元素两种选择的结果都加起来。所有表达式到结尾处,判断其值是否等于target,是的话就返回1,表示得到该表达式符合,否则返回0,表示该表达式不符合。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
return Rollback(nums, target, 0, 0);
}
private int Rollback(int[] nums, int target, int i, int value){
if(i == nums.length){
if(value == target) return 1;
else return 0;
}else{
return Rollback(nums, target, i + 1, value + nums[i]) + Rollback(nums, target, i + 1, value - nums[i]);
}
}
}
2、动态规划
有点像0-1背包问题,如果每个元素都要考虑+和-,那么对于装包就有一定困难,可不可以考虑转换一下,只考虑+,那么就转成背包问题求解了。
记数组的元素和为sum,添加-号的元素和为neg,则添加+号的元素和为sum-neg,得到表达式结果为:
(
s
u
m
−
n
e
g
)
−
n
e
g
=
t
a
r
g
e
t
(sum -neg)-neg = target
(sum−neg)−neg=target,即
n
e
g
=
s
u
m
−
t
a
r
g
e
t
2
neg=\frac{sum-target}{2}
neg=2sum−target。
因为数组中均为非负整数,所以neg也必须是非负整数,因此 sum - target 必须是非负偶数,若不符合该条件直接返回0。
因此可以在数组中选取若干元素,其和为neg,使用动态规划方法。
为什么说找neg就行呢?因为对于和为neg的元素,前面给-号,其他的给+号,表达式结果就为target,因此只需要找到数组中和为neg的元素选取方案,就找到了原问题的结果。
定义二维数组dp,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示在数组nums的前 i 个数中选取元素,使得这些元素之和等于 j 的方案数。若数组长度为n,则最终结果为
d
p
[
n
]
[
n
e
g
]
dp[n][neg]
dp[n][neg]。
当没有任何元素可以选取时,元素和只能是0,对应有一种方案数,因此,动态规划的边界条件是:
d
p
[
0
]
[
j
]
=
{
1
,
j
=
0
0
,
j
≥
0
dp[0][j] = \left \{ \begin{aligned} &1, & j =0 \\ & 0, & j \geq 0 \end{aligned} \right.
dp[0][j]={1,0,j=0j≥0
当
1
<
i
≤
n
1 < i \leq n
1<i≤n时,对于数组nums中的第 i 个元素num(i从1开始计数),遍历
0
<
j
≤
n
e
g
0 < j \leq neg
0<j≤neg,计算
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]的值:
- 当 j < n u m j < num j<num,说明不能选num,此时 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]。
- 当
j
≥
n
u
m
j \geq num
j≥num,如果不选num,方案数是
d
p
[
i
−
1
]
[
j
]
dp[i-1][j]
dp[i−1][j],如果选num,方案数是
d
p
[
i
−
1
]
[
j
−
n
u
m
]
dp[i-1][j-num]
dp[i−1][j−num],此时有
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
d
p
[
i
−
1
]
[
j
−
n
u
m
s
[
i
]
]
dp[i][j]=dp[i-1][j] + dp[i-1][j-nums[i]]
dp[i][j]=dp[i−1][j]+dp[i−1][j−nums[i]],
因此状态转移方程如下:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] , j < n u m s [ i ] d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − n u m s [ i ] ] , j ≥ n u m s [ i ] dp[i][j] = \left \{ \begin{aligned} & dp[i-1][j] , & j < nums[i] \\ & dp[i-1][j] + dp[i-1][j-nums[i]], & j \geq nums[i] \end{aligned} \right. dp[i][j]={dp[i−1][j],dp[i−1][j]+dp[i−1][j−nums[i]],j<nums[i]j≥nums[i]
结果值为 d p [ n ] [ n e g ] dp[n][neg] dp[n][neg]。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
// 每个数都有要和不要两种状态,要就+
// 设置dp[i][j],表示前i个数取元素,和为j的表达式数目
int sum = 0;
for(int num : nums)
sum += num;
int diff = sum - target;
if(diff < 0 || diff % 2 != 0) return 0;
int len = nums.length, neg = diff / 2;
int[][] dp = new int[len + 1][neg + 1];
dp[0][0] = 1;
for(int i = 1; i <= len; i++){
int num = nums[i - 1];
for(int 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];
}
}