回溯法(会超时)
本题要解决如何使表达式结果为target的问题。
既然为target,那么就一定有 left组合 - right组合 = target。
left + right等于sum
,而sum是固定的。
那么left - (sum - left) = target
所以left = (target + sum)/2 。
target是固定的,sum是固定的,left就可以求出来。
此时问题就是在集合nums中找出和为left的组合
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void backtracking(vector<int>&candicates,int sum,int target,int index){
if(sum==target){
res.push_back(path);
}
// 如果 sum + candidates[i] > target 就终止遍历
for(int ii=index;ii<candicates.size()&&sum+candicates[ii]<=target;ii++){
sum+=candicates[ii];
path.push_back(candicates[ii]);
backtracking(candicates,sum,target,ii+1);
sum-=candicates[ii];
path.pop_back();
}
}
int findTargetSumWays(vector<int>& nums, int target) {
res.clear();
path.clear();
int sum=0;
for(auto num : nums) sum+=num;
if(target>sum) return 0;
if((target+sum)%2) return 0;
int bagsize=(target+sum)/2; //推导出来的,转变为组合总和问题,bagsize就是要求的和
sort(nums.begin(),nums.end());// 需要排序
backtracking(nums,0,bagsize,0);
return res.size();
}
};
动态规划
假设加法的总和为x
(带“+”标识的),那么减法对应的总和(带“-”标识的)就是sum - x
。
所以我们要求的是使该等式 x - (sum - x) = S
成立的x的值。
即x = (S + sum) / 2
此时问题就转化为,装满容量为x背包,有几种方法。
看到(S + sum) / 2 应该担心计算的过程中向下取整有没有影响。
例如sum 是5,S是2的话其实就是无解的,所以:if ((S + sum) % 2 == 1) return 0; // 此时没有方案,两个int相加数值可能有溢出的问题
再回归到01背包问题,为什么是01背包呢?
因为每个物品(题目中的1)只用一次!
这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。
本题则是装满有几种方法。其实这就是一个组合问题了。
确定dp数组以及下标的含义
dp[ j ] 表示:填满 j(包括 j )这么大容积的包,有dp[ j ]种方法
确定递推公式
有哪些来源可以推出dp[j]呢?
不考虑nums[i]
的情况下,填满容量为j - nums[i]
的背包,有dp[j - nums[i]]
种方法。
那么只要搞到nums[i]的话,凑成dp[j]就有dp[j - nums[i]] 种方法。
那么需要把 这些方法累加起来就可以了,dp[i] += dp[j - nums[i]]
所以求组合类问题的公式,都是类似这种:dp[j] += dp[j - nums[i]]
dp数组如何初始化
从递归公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递归结果将都是0。
dp[0] = 1,理论上也很好解释,装满容量为0的背包,有1种方法,就是装0件物品。
dp[j]其他下标对应的数值应该初始化为0,从递归公式也可以看出,dp[j]要保证是0的初始值,才能正确的由dp[j - nums[i]]推导出来。
确定遍历顺序
在动态规划:关于01背包问题,你该了解这些!(滚动数组)中,我们讲过对于01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。
举例推导dp数组
输入:nums: [1, 1, 1, 1, 1], S: 3
bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4
dp数组状态变化如下:
时间复杂度O(n * m),n为正数个数,m为背包容量
空间复杂度:O(m) m为背包容量
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum=0;
for(auto num : nums) sum+=num;
if(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 ii=0;ii<nums.size();ii++){ //防止每个背包只有一个物品
for(int jj=bagsize;jj>=nums[ii];jj--){//防止物品被重复放入
dp[jj]+=dp[jj-nums[ii]];
}
}
return dp[bagsize];
}
};
本题还是有点难度,要记住,在求装满背包有几种方法的情况下,递推公式一般为:
dp[j] += dp[j - nums[i]];