题目描述:
给你一个整数数组 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来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
分析:
这道题我只想到了回溯,因为真的很适合回溯。。。
对于每个元素,先选取其负数表示,判断相加的和是否为target;然后回溯,去掉该元素,加上该元素的正数表示,再次判断相加和是否为target。老生常谈了,就不细说了。
代码如下:
class Solution {
public:
int res=0;
int findTargetSumWays(vector<int>& nums, int target) {
dfs(nums,target,0,0);
return res;
}
// dfs功能:对nums数组中的每一个元素,选取它的正值和负值,依次进行计算
void dfs(vector<int>& nums, int target,int index,int sum)
{
int len=nums.size();
// 长度越界,返回
if(index>len) return;
if(index==len)
{
// 如果访问到最后一个元素,和刚好为target,则res++
if(sum==target) res++;
return;
}
// 依次选取正数与负数
for(int i=-1;i<=1;i=i+2)
{
int tempnum=i*nums[index];
// sum加上该元素
sum+=tempnum;
// 递归访问下一元素
dfs(nums,target,index+1,sum);
// 回溯,从sum中去除该元素,选取另一个符号进行操作
sum-=tempnum;
}
return;
}
};
但是看了看题解,还有动归的做法,以及题解的思路如果没见过,一般是想不出来的。背包问题是选取与不选取的问题,但如果是这道题,是选择正数还是负数的问题,表面上看是不符合的。但是,题解进行了转化。设前面添加-号的元素的和为neg,前面添加+号的元素的和为pos,nums数组的和为sum,可以知道,pos-neg=target=(sum-neg )-neg=sum-2*neg
即:neg=(sum-target)/2
前提条件:
1.sum-target要大于0,如果sum-target<0,直接返回0,因为nums中全为正数
2.(sum-target)/2必须为偶数,否则返回0
因此,题目就变成了,从nums数组中,选取若干元素,构成和为neg的方案总数,每一种元素都面临选或者不选两种方式,这就转化为了典型的背包问题。
设dp[i][j]为从0~i个元素中,选取若干个,构成和为j的方案总数。初始化所有dp元素为0。
初始情况下,dp[0][0]+=1,因为不选取第0个元素的前提下,构成和为0的方案数为1;dp[0][nums[0]]+=1,在选取第0个元素的前提下,构成和为nums[0]的方案数为1;
之后按照递推公式进行递推即可:
对每一个dp[i][j],有:
1.不选取第i个元素:dp[i][j]+=dp[i-1][j]
2.选取第i个元素:dp[i][j]+=dp[i-1][j-nums[i]]
详细分析请看题解:
代码如下:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int n=nums.size();
int sum=0;
// 计算数组和sum
for(int i=0;i<n;i++)
sum+=nums[i];
// 如果sum-target为负数或者neg为奇数,直接返回0
if(sum-target<0||(sum-target)%2==1) return 0;
// 题目转化为求解在nums数组中,寻找若干元素,他们的和为neg
int neg=(sum-target)/2;
// dp[i][j]代表0~i个元素组成和为j的个数
// 不选取该元素:dp[i][j]=dp[i-1][j];
// 选取该元素:dp[i][j]=dp[i-1][j-nums[i]];
int dp[n][1005];
memset(dp,0,sizeof(dp));
// 不选取第0个元素,构成和为j的方法数为1
dp[0][0]+=1;
// 选取第0个元素,构成和为nums[0]的方法数为1
dp[0][nums[0]]+=1;
for(int i=1;i<n;i++)
{
for(int j=0;j<=neg;j++)
{
// 利用递推方程求解dp[i][j]
dp[i][j]+=dp[i-1][j];
if(j-nums[i]>=0)
dp[i][j]+=dp[i-1][j-nums[i]];
}
}
return dp[n-1][neg];
}
};