目标和 leetcode (494)

目标和 leetcode (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

思路 (动态规划)

思路1

dp 数组为二维,dp[i][j] 表示前i个数组合生成 j 时所有可能的种数。

前 i 个 数构成 j 可以看作 :前 i - 1 个数构成(j - nums[i]) 加上 nums[i] 或 前 i - 1 个数构成(j + nums[i]) 减去 nums[i]

所以状态转移方程 dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]


nums[i] 的取值范围是 [0,1000],target 的取值范围是 [-1000,1000]

当nums[i] = 1000时,dp[i][-1000]可以由 dp[i-1][-2000] 和 dp[i-1][0] 构成

dp[i][1000] 可以由 dp[i-1][0] 和 dp[i-1][2000] 构成。

所以 j 的范围在 -2000 到 2000

为了防止数组越界,令 int[][] dp = new int[nums.length][4001]


代码1

class Solution {
    
    
     // 0,1 背包是取和不取 这道题是 取正和取负
    public int findTargetSumWays(int[] nums, int target) {
    
    
        // dp的第二维 取值范围为[-2000,2000]跨度为4000
        int[][] dp = new int[nums.length][4001]; // dp[i][j] 表示前i个物品组合成j+2000的可能性的种数
        // 状态转移方程感觉是个递归 dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j+nums[i]]
        dp[0][nums[0] + 2000] += 1;
        dp[0][-nums[0] + 2000] += 1; //因为如果是0 的话 取正取负 和为0就有两种
        for (int i = 1; i < nums.length; i++) {
    
    
            for (int j = nums[i]; j<=4000-nums[i]; j++) {
    
    //这里要注意判别,防止越界
                dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]];
            }
        }
        return dp[nums.length - 1][target + 2000];
    }
}

注意::代码中第8-9行,使用 += 而不是 =,因为当nums[i] =0时取正还是负都是0,但是算两种方法


思路2

对于每个数,我们有两种选择。(1)让它的符号为正 (2)让它的符号为负

所以,可以根据我们的选择将它们分为两堆。

设选择为正的这一堆数字的和为pos,设选择为负的这一堆数字和为neg。

所有数字的总和为 sum,目标为 target。

我们有以下表达式:
{ n e g + p o s = s u m ( 1 ) p o s − n e g = t a r g e t ( 2 ) \left\{ \begin{aligned} neg +pos & = sum &(1)\\ pos - neg & = target &(2) \\ \end{aligned} \right. { neg+posposneg=sum=target(1)(2)
用(1)+(2)得 2 ∗ p o s = s u m + t a r g e t 2 *pos = sum + target 2pos=sum+target

所以我们可以把该问题转化为从一堆数中取任意个,使它们的和为 ( s u m + t a r g e t ) / 2 (sum + target)/2 (sum+target)/2。总共有多少种取法?

代码2

class Solution {
    
    
    public int findTargetSumWays(int[] nums, int target) {
    
    
        int sum = 0;
        for (int num : nums) {
    
    
            sum += num;
        }
        if ((target + sum) % 2 != 0) return 0; // target + sum 不能被 2 整除,表示无法选数构成pos

        int pos = Math.abs((sum + target) / 2);
        int[] dp = new int[pos + 1]; // dp[i]表示 从中 nums中取任意个数,能构成 i 的种数
        dp[0] = 1;
        for (int i = 0; i < nums.length; i++) {
    
    
            for (int j = pos; j >= nums[i]; j--) {
    
    
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[pos];
    }
}

猜你喜欢

转载自blog.csdn.net/jiaweilovemingming/article/details/124388345