目标和
题目
给你一个整数数组 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 <= 200 <= nums[i] <= 10000 <= 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 协议 ,转载请注明出处!