【LeetCode-背包】目标和

题目描述

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

输入: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。

说明:

  • 数组非空,且长度不会超过 20 。
  • 初始的数组的和不会超过 1000 。
  • 保证返回的最终结果能被 32 位整数存下。

题目链接: https://leetcode-cn.com/problems/target-sum/

思路

假设我们将数组分为两部分,两部分的和分别为 x,y,数组中所有元素的和为 sum,则有 x + y = sum, x - y = S,可得 x = (sum + S) / 2. 所以,问题就变为了数组能否分为两部分,其中一部分的和为 (sum + S) / 2。可以使用和等和子集一样的方法来做。

  • 状态定义:dp[i][j] 表示下标范围为 [0, i] 内数组元素中和为 j 的个数;
  • 状态转移:对于第 i 个数,可以选也可以不选,所以 dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]];
  • 边界条件:dp[i][0] = 1,含义是范围 [0, i] 内元素和为 0 的个数为 1,也就是所有的数都不选;dp[0][nums[0]] += 1 if nums[0]<=x,本来 dp[0][0] = 1,如果 nums[0] = 0,此时 dp[0][0] 就等于 2 了,也就可以选择 nums[0],也可以不选择 nums[0],因为 nums[0] = 0,所以这两种方法是一样的。

代码如下:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        if(nums.empty() && S>0) return 0;

        int s = 0;
        for(int i=0; i<nums.size(); i++){
            s += nums[i];
        }
        if(s<S) return 0;
        s += S;

        if(s%2!=0) return 0;
        int target = s / 2;

        vector<vector<int>> dp(nums.size(), vector<int>(target+1, 0));
        for(int i=0; i<nums.size(); i++) dp[i][0] = 1; // 边界条件
        /*if(nums[0]<=target) dp[0][nums[0]] = 1;
        if(nums[0]==0) dp[0][nums[0]] = 2;*/
        if(nums[0]<=target) dp[0][nums[0]] += 1; // 这种写法和上面注释的写法是一样的

        for(int i=1; i<nums.size(); i++){
            for(int j=0; j<=target; j++){
                dp[i][j] = dp[i-1][j];
                if(j>=nums[i]){
                    dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]];
                }
            }
        }
        return dp[nums.size()-1][target];
    }
};

空间复杂度优化:
使用和等和子集一样的方法对空间复杂度进行优化:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        if(nums.empty() && S>0) return 0;

        int s = 0;
        for(int i=0; i<nums.size(); i++){
            s += nums[i];
        }
        if(s<S) return 0;
        s += S;

        if(s%2!=0) return 0;
        int target = s / 2;

        vector<int> dp(target+1, 0);
        dp[0] = 1;
        if(nums[0]<=target) dp[nums[0]] += 1;
        for(int i=1; i<nums.size(); i++){
            for(int j=target; j>=nums[i]; j--){
                dp[j] += dp[j-nums[i]];
            }
        }
        return dp[target];
    }
};
posted @ 2020-08-27 22:28  Flix  阅读(300)  评论(0编辑  收藏  举报