一、题目描述
给你一个整数数组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
二、问题分析
乍一看这道题跟动态规划没什么关系。但是仔细分析题目,“向数组中的每个整数前添加’+‘
或’-’
”,这里不就是取不取的问题吗?那就可以用动态规划0-1背包来解决问题。那要怎样确定dp数组?怎样确定递推公式?这里先确定背包的最大容量bagSize
,这要看你对数学的敏感程度了。
先记数组的元素和为sum,记在前面添加“-”的元素和为sub,那么前面添加“+”的元素和为sum-sub,那么
(
s
u
m
−
s
u
b
)
−
s
u
b
=
t
a
r
g
e
t
(sum-sub)-sub=target
(sum−sub)−sub=target,则
s
u
b
=
(
s
u
m
−
t
a
r
g
e
t
)
/
2
sub=(sum-target)/2
sub=(sum−target)/2。由于sum和target已经确定,那么sub也就是确定得,就可以确定背包最大容量为sub。从数组取出部分元素使得总和刚好为sub,而题目要求的是方法数,转化为装满容量为sub的背包有几种方法。
注意:从题目提示中知道数组所元素是非负整数,那么sub必须也非负整数,即target<=sum,在提示中target可以是负数,当全部数组元素都取“-”时,target为最小,即target>=-sum,也就是abs(target)<=sum
。还有就是sum-target必须能够整除2,即(sum - target) % 2 == 0
。
- 确定dp数组及下标的含义:dp[ j j j]表示装满 j j j(包括 j j j)这么大容量的背包,有dp[ j j j]种方法。
- 确定递推公式:在不考虑nums[
i
i
i]的情况下,装满容量为
j
j
j-nums[
i
i
i]的背包,有dp[
j
j
j-nums[
i
i
i]]种方法,那么只要找到nums[
i
i
i],凑成dp[
j
j
j]就有dp[
j
j
j-nums[
i
i
i]]种方法,所以递推公式为:
dp[j] += dp[j-nums[i]]
- 初始化dp数组:从递推公式看出,只要初始化dp[0]就可以了,而装满容量为0背包有1种方法,也就是装0件物品,即
dp[0]=1
,而其他数则初始为0就可以。 - 确定遍历顺序:遍历顺序逻辑与0-1背包是一样的,使用一维数组,遍历物品的for循环放在外层,遍历背包的
for
循环放在内层,且内层for
循环使用倒叙遍历。
三、代码实现
// 编程软件:VS2019
// 参考书籍:代码随想录
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
// 动态规划:时间复杂度O(n×(sum-target)),空间复杂度O(sum-target)
int findTargetSumWays(vector<int>& nums, int target) {
int sum = 0;
for (int i = 0; i < nums.size(); i++) sum += nums[i];
if (abs(target) > sum) return 0; // target绝对值大于sum,就直接返回false
if ((sum - target) % 2 != 0) return 0; // 不能整除2,就直接返回false
int bagSize = (sum - target) / 2; // 最大背包容量
vector<int> dp(bagSize + 1, 0);
dp[0] = 1;
// 0-1背包逻辑
for (int i = 0; i < nums.size(); i++) {
for (int j = bagSize; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[bagSize];
}
int main() {
vector<int>nums = { 1,1,1,1,1 };
int target = 3;
cout << findTargetSumWays(nums, target);
}
// 结果:5
四、小结
这道题首先要看得出与动态规划有关(这就要平时多做多看一些动态规划得题目了),然后再去推导出最大背包容量,再转化成装满容量sub容量方法有几种,而对于求装满背包有几种方法问题,递推公式一般都为dp[j] += dp[j-nums[i]]
。
力扣:416. 分割等和子集