leetcode494. 目标和 !

传送门

题目: 给定一个非负整数数组,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]] 代表两次结果都考虑:

  1. 不选nums[i]: dp[j] = dp[j] 跟上一行的结果一样
  2. 选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];
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值