目标和
题目
给你一个整数数组 nums
和一个整数 target
向数组中的每个整数前添加 '+'
或 '-'
,然后串联起所有整数,可以构造一个表达式 :
例如,
nums = [2, 1]
,可以在2
之前添加'+'
,在1
之前添加'-'
,然后串联起来得到表达式"+2-1"
返回可以通过上述方法构造的、运算结果等于target
的不同表达式的数目示例1:
1
2
3
4
5
6
7
8输入: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:
1
2输入:nums = [1], target = 1
输出:1
提示:
1 <= nums.length <= 20
0 <= nums[i] <= 1000
0 <= sum(nums[i]) <= 1000
-1000 <= target <= 1000
思路+代码
思路
本题可以采用回溯、动态规划方法分别完成目标和解算
- 回溯:
- 数组
nums
的每个元素都可以添加符号+
或-
,因此每个元素有2
种添加符号的方法,n
个数对应 $2^n$ 种不同的表达式 - 如果表达式的结果等于目标数
target
,则该表达式即为符合要求的表达式 - 可以使用回溯的方法遍历所有的表达式,回溯过程中维护一个计数器
count
,当遇到一种表达式的结果等于目标数target
时,将count
的值加1
采用队列的BFS方法也可以遍历所有的表达式,但是这种方式有入队出队操作,在LeetCode提交会超时
- 动态规划
- 记数组的元素和为
sum
,添加-
号的元素之和为neg
,则其余添加+
的元素之和为sum - neg
,得到的表达式的结果为:(sum − neg) − neg = sum − 2 * neg = target
,即:neg = (sum − target) / 2
- 上式成立的前提是
sum - target
是非负偶数。若不符合该条件可直接返回0
- 上式成立,问题转化成在数组
nums
中选取若干元素,使得这些元素之和等于neg
,计算选取元素的方案数- 定义二维数组
dp
,其中dp[i][j]
表示在数组nums
的前i
个数中选取元素,使得这些元素之和等于j
的方案数。假设数组nums
的长度为n
,则最终答案为dp[n][neg]
- 当没有任何元素可以选取时,元素和只能是
0
,对应的方案数是1
,因此动态规划的边界条件是:
$$dp\lbrack0\rbrack\lbrack j\rbrack=\left\{\begin{array}{l}1,\;j=0\\0,\;j\geq1\end{array}\right.$$
- 定义二维数组
- 当
1 ≤ i ≤n
时,对于数组nums
中的第i
个元素num
(i
的计数从1
开始),遍历0 ≤ j≤ neg
,计算dp[i][j]
的值- 如果
j < num
,则不能选num
,此时有dp[i−1][j]
; - 如果
j ≥ num
,则如果不选num
,方案数是dp[i−1][j]
,如果选num
,方案数是dp[i−1][j−num]
,此时有dp[i][j]=dp[i−1][j]+dp[i−1][j−num]
- 如果
因此状态转移方程如下:
$$dp\lbrack i\rbrack\lbrack j\rbrack=\left\{\begin{array}{l}d\lbrack i-1\rbrack\lbrack j\rbrack,\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;j<num\lbrack i\rbrack\\d\lbrack i-1\rbrack\lbrack j\rbrack+d\lbrack i-1\rbrack\lbrack j-num\lbrack i\rbrack\rbrack,\;j\geq num\lbrack i\rbrack\end{array}\right.$$
最终得到 dp[n][neg]
的值即为答案
代码实现
回溯:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Solution {
public:
void dfs(vector<int>& nums, int target, int index, int sum, int& count)
{
if (index == nums.size())
{
if (sum == target)
count++;
}
else
{
dfs(nums, target, index+1, sum+nums[index], count);
dfs(nums, target, index+1, sum-nums[index], count);
}
}
int findTargetSumWays(vector<int>& nums, int target) {
int count = 0;
dfs(nums, target, 0, 0, count);
return count;
}
};动态规划:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class Solution {
public int findTargetSumWays(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.length, neg = diff / 2;
int[][] dp = new int[n + 1][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];
}
}
由于 dp
的每一行的计算只和上一行有关,因此可以使用滚动数组的方式,去掉 dp
的第一个维度,将空间复杂度优化到 O(neg)
实现时,内层循环需采用倒序遍历的方式,这种方式保证转移来的是 dp[i−1][]
中的元素值
- 动态规划(优化空间复杂度):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Solution {
public int findTargetSumWays(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 neg = diff / 2;
int[] dp = new int[neg + 1];
dp[0] = 1;
for (int num : nums) {
for (int j = neg; j >= num; j--) {
dp[j] += dp[j - num];
}
}
return dp[neg];
}
}
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/queue-stack/ga4o2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
本文作者: 贾明晖
本文链接: http://minghuijia.cn/2022/03/16/%E7%9B%AE%E6%A0%87%E5%92%8C/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!