494. 目标和

494. 目标和

题目链接:494. 目标和(中等)

给你一个整数数组 nums 和一个整数 target

向数组中的每个整数前添加 '+''-' ,然后串联起所有整数,可以构造一个 表达式

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1"

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 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
+1 + 1 + 1 + 1 - 1 = 3

示例 2:

输入:nums = [1], target = 1
输出:1

提示:

  • 1 <= nums.length <= 20

  • 0 <= nums[i] <= 1000

  • 0 <= sum(nums[i]) <= 1000

  • -1000 <= target <= 1000

解题思路

假设符号为+的整数(非负数)总和为left,符号为-的整数(非负数)总和为right,那么有公式target = left - right以及sum = left + right。根据这两个公式,可以得到2*left = target + sumleft = (target + sum) / 2

于是题目就可以转换为:装满容量为left的背包,最多有几种方法。这可以用01背包来解。

首先,我们要两种情况需要先排除:

  • target的绝对值大于sum时,不可能实现

  • 从公式2*left = target + sum可以看出,2*left一定时偶数,所以target + sum也必须保证时偶数。

使用一维数组的解题步骤:

  1. 确定dp数组以及其下标的含义

    dp[j]表示装满背包容量为j的有多少种方法

  2. 确定递推公式

    不考虑nums[i]的情况下,填满容量为j - nums[i]的背包,有dp[j - nums[i]]种方法。

    也就是当前填满容量为j的背包的方法数 = 之前填满容量为j的背包的方法数 + 之前填满容量为j - nums[i]的方法数。于是得到递推公式为:dp[j] = dp[j] + dp[j - nums[i]]

  3. dp数组的初始化

    用0件物品(在下面的代码中可以看到遍历物品的i是从下标0开始的,所以此时并没有物品)装满背包容量为0的背包,有一种方法。

    所以dp[0] = 1

  4. 确定遍历顺序

    一维dp解决0-1背包问题,先正序遍历物品,再倒叙遍历背包。

  5. 举例推导dp数组(略)

C++

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
        }
        if (abs(target) > sum || ( target + sum ) % 2 == 1) {
            return 0;
        }
        int bagSize = ( sum + target ) / 2;
        vector<int> dp(bagSize + 1, 0);
        dp[0] = 1;
        for (int i = 0; i < nums.size(); i++) {
            for (int j = bagSize; j >= nums[i]; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[bagSize];
    }
};

JavaScript

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var findTargetSumWays = function(nums, target) {
    let sum = 0;
    for (let i = 0; i < nums.length; i++) {
        sum += nums[i];
    }
    if (Math.abs(target) > sum || (sum + target) % 2 == 1) {
        return 0;
    }
    let bagSize = (sum + target) / 2;
    const dp = new Array(bagSize + 1).fill(0);
    dp[0] = 1;
    for (let i = 0; i < nums.length; i++) {
        for (let j = bagSize + 1; j >= nums[i]; j--) {
            dp[j] += dp[j - nums[i]];
        }
    }
    return dp[bagSize];
};
  • 时间复杂度:O(n × m),n为正数个数,m为背包容量

  • 空间复杂度:O(m)

另外,还可以用二维数组解决。不过需要注意dp数组的初始化,因为nums[0] = 0是一种特殊的情况。给出C++代码如下。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
        }
        if (abs(target) > sum || ( target + sum ) % 2 == 1) {
            return 0;
        }
        int bagSize = ( sum + target ) / 2;
        vector<vector<int>> dp(nums.size(),vector<int>(bagSize + 1, 0));
        // 特殊!!!!!!!!!!!!!!!!!!!!!!
        // 因为 0 既可以用 -0 表示, 也可以用 +0 表示
        // 所以将 0 (不是物品的下标,而是物品的重量或价值) 填满容量背包为 0 的背包中,有两种方法
        if (nums[0] == 0) {
            dp[0][0] = 2;
        } else {
            dp[0][0] = 1;
        }
        for (int j = 1; j < bagSize + 1; j++) {
            if (j == nums[0]) dp[0][j] = 1;
        }
        for (int i = 1; i < nums.size(); i++) {
            for (int j = 0; j < bagSize + 1; j++) {
                if (j < nums[i]) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];
                }
            }
        }
        return dp[nums.size() - 1][bagSize];
    }
};

 

 

 

 

 

 

posted @ 2022-03-03 22:03  wltree  阅读(45)  评论(0编辑  收藏  举报