题目描述
解决方法
方法一 暴力法
这道题目可以使用回溯法做,就是对数组中每个数前分别加上正号和负号进行遍历即可,时间复杂度为 O ( 2 n ) O(2^{n}) O(2n)
class Solution {
public:
int count=0;
int findTargetSumWays(vector<int>& nums, int target) {
dfs(nums, 0, target);
return count;
}
void dfs(vector<int>& nums, int index, int leftNum){
int n = nums.size();
if(index>=n){
if(leftNum==0) count++;
return;
}
dfs(nums, index+1, leftNum+nums[index]);
dfs(nums, index+1, leftNum-nums[index]);
}
};
方法二 动态规划
说实话,这个动态规划太取巧了,让我想起了高中时做数学题的感觉。要使用这个方法需要对题目进行分析和化归才行。
首先,我们先分析一下问题,问题中对每个数字只有两种操作,一种是加 +,一种是加 - ,所以,我们根据数组数字前的符号将数字分为两类,一类为正号数字和
p
o
s
pos
pos,一类为负号数字和
n
e
g
neg
neg(不包含数字前的符号,所以和为正),所以问题可以转化为:
p
o
s
−
n
e
g
=
t
a
r
g
e
t
pos-neg=target
pos−neg=target
将数组中所有的元素求和,得到一个
s
u
m
sum
sum,于是有:
p
o
s
+
n
e
g
=
s
u
m
pos+neg=sum
pos+neg=sum
将这个式子带入到上一个公式中,可得
(
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
由于题干中数组中每个元素都是非负整数,所以
n
e
g
≥
0
neg≥0
neg≥0,根据这个条件,我们可以在程序开始前先进行一次判断,如果整个数组元素和和
t
a
r
g
e
t
target
target差值除以2不为非负整数,我们就可以直接返回0。
对于一个给定的数组,
n
e
g
neg
neg是个常数值,我们的问题其实就转化成了从数组中寻找和为
n
e
g
neg
neg的子序列个数。
于是,我们定义一个数组
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j],表示从前
i
i
i个数中找到和为
j
j
j的子序列和的个数。
i
i
i的取值为
[
0
,
n
]
[0, n]
[0,n],
j
j
j的取值为
[
0
,
n
e
g
]
[0, neg]
[0,neg]。
对于
i
i
i为0的情况,当
j
=
0
j=0
j=0时,
d
p
[
i
]
[
j
]
=
1
dp[i][j]=1
dp[i][j]=1,其余
d
p
[
i
]
[
j
]
=
0
dp[i][j]=0
dp[i][j]=0。
中间的转移方程是这样的:
当
j
<
n
u
m
s
[
i
]
j<nums[i]
j<nums[i]时,
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
s
[
i
]
j>=nums[i]
j>=nums[i]时,
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
]
dp[i][j]
dp[i][j],即我要求得前
i
i
i个数字中找到和为
j
j
j的子序列数目,可以拆分成为不包含第
i
i
i个数字的子序列数目(
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
dp[i][j]=dp[i-1][j]
dp[i][j]=dp[i−1][j]),以及包含第
i
i
i个数字的子序列数目(
d
p
[
i
−
1
]
[
j
−
n
u
m
s
[
i
]
]
dp[i-1][j-nums[i]]
dp[i−1][j−nums[i]])。
最后的代码如下:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = 0;
for (int& num : nums) {
sum += num;
}
int diff = sum - target;
if (diff < 0 || diff % 2 != 0) {
return 0;
}
int n = nums.size(), neg = diff / 2;
vector<vector<int>> dp(n + 1, vector<int>(neg + 1));
dp[0][0] = 1;
for (int i = 1; i <= n; 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[n][neg];
}
};