题目: 给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。数组长<=20
输入: nums: [1, 1, 1, 1, 1], S: 3 输出:5
-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
一共有5种方法让最终目标和为3。
面试如果直接说第二种方法,其实并不会加分。给出常规dfs解法
方法1. 暴力解法dfs
这道题而言,暴力解法是完全可以的,而且不会超时,因为题目中说了数组长度不会超过20,20个数字的序列,组合方式撑死了2的20次方种,即:1024 × 1024
也就是说,可以把数组中每个数字前面都用负号和正号,然后进行组合的求和,并判断这个和是否会等于S,然后就标记,最后统计出等于S的组合个数就好了。
关于 递归直接返回答案 和 递归过程修改全局遍历的答案 两种互换
c++: dfs直接返回答案
int findTargetSumWays(vector<int>& nums, int S) {
return dfs(nums, S, 0);
}
int dfs(vector<int>& nums, uint target, int len) {
if (target == 0 && len == nums.size()) return 1;
if (len >= nums.size()) return 0;
int ans = 0;
ans += dfs(nums, target - nums[len], len + 1);
ans += dfs(nums, target + nums[len], len + 1);
return ans;
}
Java:全局遍历存放答案
int ans = 0;
public int findTargetSumWays(int[] nums, int S) {
helper(nums, S, 0, 0);
return ans;
}
void helper(int[] nums, int sum, int start, int pathSum) {
if (start == nums.length) {
if (sum == pathSum) ans++;
return;
}
helper(nums, sum, start + 1, pathSum + nums[start]);
helper(nums, sum, start + 1, pathSum - nums[start]);
}
方法2. 动态规划0-1背包
【变换】:
假设原数组为S,目标值为target,那么原数组必然可以分成两个部分,一个部分里面的元素前面需要加-,即运算的时候应该是做减法,另一个部分里面的元素前面需要加+,即运算的时候应该是做加法;
我们将做加法部分的数组记为P,做减法部分的数组记为N,
举个例子,例如S = {1,2,3,4,5},target = 3,
那么有一种可以是1-2+3-4+5,即P = {1,3,5},N = {2,4};
于是我们可以知道:
target = sum(P) - sum(N);
那么:
sum(P) + sum(N) + sum(P) - sum(N)
= sum(S) + target
= 2sum(P);
那么sum(P) = [target + sum(S)] / 2;
根据以上的推导,我们可以得到这样的结论:我们需要找到这样一个子序列P,使得子序列之和等于原序列之和与目标值的和的一半,我们需要找到这样子序列的数目。
这不是0-1背包问题吗!
dp[j] = dp[j] + dp[j-nums[i]]
代表两次结果都考虑:
- 不选nums[i]:
dp[j] = dp[j]
跟上一行的结果一样 - 选nums[i] :
dp[j] = dp[j-nums[i]]
找对应dp的差值
注意判断sum要>= S
Java版本:
public int findTargetSumWays(int[] nums, int S) {
int sum = 0;
for (int num : nums) sum += num;
if (S > sum || (S + sum) % 2 != 0) return 0;//S+sum不一定是偶数
sum = (S + sum) / 2;
// 转换成求0-1背包,和是 sum
int[] dp = new int[sum + 1];
dp[0] = 1;//初始不是1的话,dp方程不可能得到大于0的值
for (int i = 0; i < nums.length; ++i) {
for (int j = sum; j >= nums[i]; --j) {
dp[j] = dp[j] + dp[j - nums[i]];
}
}
return dp[sum];
}
C++版本:
int findTargetSumWays(vector<int>& nums, int S) {
long sum = 0;
for (int &num : nums) sum += num;
if ((S + sum) % 2 == 1 || S > sum) return 0;// 要判断 S<=sum
S = (S + sum) / 2;
vector<int> dp(S + 1);
dp[0] = 1;
/*---从这里开始就只考虑找几个数来组成target----*/
// j>=nums[i]才有效,因为看索引:方程里有dp[j-nums[i]]
// 或者理解:j是递减的,如果当前j不满足>=num,
// 那么这更新第i行时,小于j的肯定不用加dp[j-num],就还是上一行的dp[j]
for (int &num : nums) {
for (int j = S; j >= num; --j) {
dp[j] += dp[j - num];
}
}
return dp[S];
}