🍕🎉🥓🎊🚗🐷😱❤😀❣🥙🎇🕵️♀️😎🎑🎁🎉😚🎇
题目:
- 给你一个非负整数数组
nums
和一个整数target
。向数组中的每个整数前添加'+'
或'-'
,然后串联起所有整数,可以构造一个 表达式 :- 例如,
nums = [2, 1]
,可以在2
之前添加'+'
,在1
之前添加'-'
,然后串联起来得到表达式"+2-1"
。
- 例如,
- 返回可以通过上述方法构造的、运算结果等于
target
的不同 表达式 的数目。
示例:
- 输入: n u m s = [ 1 , 1 , 1 , 1 , 1 ] , t a r g e t = 3 nums = [1,1,1,1,1], target = 3 nums=[1,1,1,1,1],target=3
- 输出: 5 5 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
解题思路一:(动规一维数组😋)
- 抽象为01背包: 本题要使表达式结果为
target
,既然要为target
,那么就一定有总和1 - 总和2 = target
,这里总和1
是'+'
的数的总和,总和2
是'-'
的数的总和。又总和1 + 总和2 = sum
, sum是固定的,可推出总和2 = sum - 总和1
。继而可由公式总和1 - (sum - 总和1) = target
推导出总和1 = (target + sum) / 2
,由于target
和sum
都是固定的,因此总和1
就能求出来。此时问题就抽象为在集合nums
中找出和为总和1
的组合。假设加法的总和为x
,那么减法的总和就是sum - x
。我们要求的是x - (sum - x) = target
,即x = (target + sum) / 2
,此时问题就转化为,装满容量为x的背包,有几种方法。这里的x
即为背包容量大小。 - 确定dp数组及下标含义:
dp[j]
表示填满容量大小为j
的背包,有dp[j]
种方法。 - 确定递推公式:
dp[j] += dp[j - nums[i]]
。 - dp数组初始化:
dp[0] = 1
- 确定遍历顺序:
nums
放在外循环,target
放在内循环,内循环倒序。
版本一:Java
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0, n = nums.length;
for(int x: nums) sum += x;
if(Math.abs(target) > sum) return 0;
if((target + sum) % 2 == 1) return 0;
int bagSize = (target + sum) / 2;
int[] dp = new int[bagSize + 1];
dp[0] = 1;
for(int i = 0; i < n; i++)
for(int j = bagSize; j >= nums[i]; j--)
dp[j] += dp[j - nums[i]];
return dp[bagSize];
}
}
版本二:C++
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int n = nums.size(), sum = 0;
for(auto x: nums) sum += x;
if(abs(target) > sum) return 0;
if((target + sum) % 2) return 0;
int bagSize = (target + sum) / 2;
vector<int> dp(bagSize + 1, 0);
dp[0] = 1;
for(int i = 0; i < n; i++)
for(int j = bagSize; j >= nums[i]; j--)
dp[j] += dp[j - nums[i]];
return dp[bagSize];
}
};
时间复杂度:
O
(
n
×
m
)
O(n × m)
O(n×m),n为正数个数,m为背包容量
空间复杂度:
O
(
m
)
O(m)
O(m),m为背包容量
解题思路二:动规(二维数组😀)
- 确定dp数组及下标含义:
dp[i][j]
表示前i
个数,总和为j
的所有方案的总和。 - 确定递推公式: 由第
i
个数为正
或负
可以划分为两个集合,分别为 ①nums[i]
取正,此时dp[i][j] = dp[i - 1][j - nums[i]]
,j-nums[i]
表示总和为j
是,nums[i]
为正,说明第i
个数选了正,因此总和j
需要减去第i
个数。② 同理,nums[i]
取负,则dp[i][j] = dp[i - 1][j + nums[i]]
。而结果是求总方案数,因此dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]
。 - dp数组初始化:
dp[0][0] = 1
表示一个数都不选的时候总和为0,此时只有这一种方案,故初始为1。
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
if(target < -1000 || target > 1000) return 0; // 题目中 target范围为[-1000, 1000];
int n = nums.size(), offset = 1000; // 需要定义一个偏移量,因为状态可能为负,但是数组下标不能为负
vector<vector<int>> dp(n + 1, vector<int>(2001));
dp[0][offset] = 1; // 数组初始化
for(int i = 1; i <= n; i++)
for(int j = -1000; j <= 1000; j++){
if(j - nums[i - 1] >= -1000)
dp[i][j + offset] += dp[i - 1][j - nums[i - 1] + offset];
if(j + nums[i - 1] <= 1000)
dp[i][j + offset] += dp[i - 1][j + nums[i - 1] + offset];
}
return dp[n][target + offset];
}
};