题目描述
原题链接:494. 目标和
解题思路
(1)回溯法
本题的特点是nums
中每个元素只能使用一次,分别试探加上nums[index]
和减去nums[index]
,然后递归的遍历下一个元素index + 1
。
class Solution {
public:
int res = 0;
void backtracking(vector<int>& nums, int target, int index) {
if(index == nums.size()) {
if(target == 0) res++;
return ;
}
backtracking(nums, target - nums[index], index + 1);
backtracking(nums, target + nums[index], index + 1);
}
int findTargetSumWays(vector<int>& nums, int target) {
backtracking(nums, target, 0);
return res;
}
};
(2)动态规划
本题中的数都为非负数,目标要求是选取组成正数的数与负数的数,让其和为target
,因此我们可以将这个题中的数划分为两个集合,一个是要组成正数的集合,设容量为pos
,一个是要组成负数的集合,设容量为neg
。
由题中要求可得pos - neg = target
,pos + neg = sum
,联立两式,可得2 * pos = target + sum
,因此我们就可以进行第一个判定,当target + sum
不为偶数时,可知一定不能组合出target
直接返回false
即可。当为偶数时,我们要找到可以组成pos
(pos = (target + sum) / 2
)的组合。问题就可以转变为,当背包容量为pos
时,选取nums里的数,有多少种组合方式可填满背包。
- 动态规划五步曲:
(1)dp[j]含义: 装满背包容量为j的方式个数。
(2)递推公式: dp[j] += dp[j - nums[i]],装入nums[i]之前,容量为j - nums[i]时的方式个数dp[j - nums[i]],再加上装入nums[i]之后,容量为j时之前的方式个数dp[j],进而得到背包容量为j时,总的方式个数。
(3)dp数组初始化: dp[0] = 1,容量为0时,仅有一种方式可以成立,即选择数字0。
(4)遍历顺序: 先物品、再背包,内层按从大到小遍历的滚动数组。
(5)举例:
输入: nums: [1, 1, 1, 1, 1], S: 3
此时,正数最大为4,里面只有1,因此dp[j]长度为4。
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sumNums = 0;
for(int i = 0; i < nums.size(); i++) sumNums += nums[i];
// target超过总和或者不满足pos为偶数的情况,直接返回0
if(abs(target) > sumNums || (sumNums + target) % 2 != 0) return 0;
int dp[10001] = {0};
dp[0] = 1;
int pos = (sumNums + target) / 2;
for(int i = 0; i < nums.size(); i++) {
for(int j = pos; j >= nums[i]; j--) {
// 组合情况要累计
dp[j] += dp[j - nums[i]];
}
}
return dp[pos];
}
};
参考文章:494. 目标和、目标和、目标和(详细C++代码动态规划详细思路分析)