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

动态规划

一维数组

这段代码是一个使用一维动态规划数组解决目标和问题的实现。代码优化了空间复杂度,仅使用一维数组来记录状态。以下是对该代码的逐行注释和说明:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        // 计算nums数组的总和
        int sum = 0;
        for(int i = 0; i < nums.size(); i++) sum += nums[i];

        // 如果目标target的绝对值大于总和,或者(target + sum)为奇数,则不可能实现目标
        if(abs(target) > sum || (sum + target) % 2 == 1) return 0;

        // 计算动态规划的新目标值left,即数组可以达到的一个子集和
        int left = (sum + target) / 2;

        // 初始化动态规划的数组,大小为left+1
        // dp[j]表示能够组合出和为j的方法数
        vector<int> dp(left + 1, 0);
        // 初始状态,没有元素时只能组成和为0的一种方式
        dp[0] = 1;

        // 遍历nums数组中的每个数字,更新dp数组
        for(int i = 0; i < nums.size(); i++) {
            // 从大到小更新dp数组,避免重复使用nums[i]更新dp[j]
            for(int j = left; j >= nums[i]; j--) {
                // 更新dp[j],dp[j-nums[i]]表示新增nums[i]之前组成j-nums[i]的方法数
                // 现在可以通过加上nums[i]来达到和为j
                dp[j] += dp[j - nums[i]];
            }
        }
        // 返回最终能组成和为left的方法数,即为原问题的解
        return dp[left];
    }
};

这段代码通过转换问题,将原问题简化为一个子集和问题:它首先通过检查目标值target与数组nums元素总和sum的关系来确定问题是否有解。如果target可通过添加正负号到nums的元素来达成,那么(sum + target)必须是偶数。接着,将问题转换为寻找数组子集,其和等于(sum + target) / 2

一维动态规划数组dp用来记录达到各个和j的方法数。dp[0]初始化为1,因为和为0总是有一种方法(不选取任何数字)。随后,代码通过遍历nums中的每个数字来逐步构建达到目标子集和的方法数。由于一个数字只能用一次,遍历dp数组时需要逆向更新,防止同一个数字被多次使用。

最终,dp[left]即表示原问题的答案,即通过添加正负号到nums的元素来达成目标target的方法数。

二维数组

这段代码是解决“目标和”问题的一种动态规划解法。问题的核心是在于数列中的每个数前添加"+“或”-",使得最终表达式的结果等于给定的target。代码的基本逻辑是利用动态规划(DP)找出达到特定和(这里是变形后的target)的方法数。下面是对代码的逐行解释:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        // 首先遍历给定数组nums,计算所有元素的总和sum
        for(int i = 0; i < nums.size(); i++)
            sum += nums[i];

        // 如果target的绝对值大于sum,或者(sum + target)是奇数,则无法通过添加"+"和"-"达到target,返回0
        if(abs(target) > sum || ((sum + target) % 2) == 1) return 0;

        // 计算需要达到的正数和left,这部分是将原问题转化为一个子集和问题
        int left = (sum + target) / 2;
        int n = nums.size();

        // 初始化动态规划表,dp[i][j]表示用前i个数可以构造出和为j的方法数
        vector<vector<int>> dp(n + 1, vector<int>(left + 1, 0));

        // base case,没有数字时唯一的方法就是构造出和为0
        dp[0][0] = 1;

        // 动态规划填表过程
        for(int i = 1; i <= n; i++) {
            for(int j = 0; j <= left; j++) {
                // 先继承前i-1个数构造出和为j的方法数
                dp[i][j] = dp[i - 1][j];
                // 如果当前和j大于等于当前数字nums[i-1],则当前数字可以被加入构造和
                // 此时的方法数等于不加当前数字时的方法数加上加了当前数字后的方法数
                if(j >= nums[i - 1])
                    dp[i][j] += dp[i - 1][j - nums[i - 1]];
            }
        }

        // 返回使用所有数字可以构造出和为left的方法数
        return dp[n][left];
    }
};

这个DP解法的核心思想是将原问题转化为一个子集和问题(一个经典的动态规划问题)。通过计算所有数字总和sum和目标值target的关系,我们可以得知需要达到的新目标和left。然后,通过构建一个二维DP表来记录达到每个可能和的方法数,最终我们得到的dp[n][left]就是问题的解答。

在这个问题中,dp数组是动态规划中的一种常见技巧,用于存储中间结果,以避免重复计算,并最终解决问题。dp数组的含义和初始化都是解题的关键部分。

dp数组的含义

在这个特定问题中,dp[i][j]代表的是,使用nums数组中的i个数,能够组成和为j的不同表达式的数量。这里的“前i个数”是指索引从0到i-1nums数组中的元素。

注意:是前i个

初始化
  • dp[0][0] = 1:这表示不使用任何数字,构成和为0只有一种方法,即没有任何操作。这是递归解决方案的基础案例,也是动态规划的初始条件。
  • 对于dp[0][1]dp[0][2]等,它们被初始化为0,因为没有使用任何数字时,不可能组成除0以外的任何和。这反映了在不考虑任何nums元素的情况下,不可能通过添加正负号来得到非零的和。
为什么dp[0][1]dp[0][2]是0

这些初始化为0的值表示在不使用nums数组中的任何数时,无法达成任何非零的目标和。这是一个重要的初始条件,因为它确立了构建解决方案时的基线。简而言之,没有数字可以选择意味着无法实现任何非零和,因此这些情况的可能性数量是0。

如何理解left=(sum+target)/2

这个转换基于一个数学技巧,通过将问题转换为寻找和为特定值的子集的数量,来简化问题。原问题要求我们通过在数组nums的每个元素前添加正号或负号,来找到所有可能达到目标和target的方式。

如果我们设所有正号的数之和为P,所有负号的数之和的绝对值为N,那么有:

  1. 总和sum = P + N
  2. 目标和target = P - N

通过解这两个方程,我们可以得到P = (sum + target) / 2。也就是说,问题转化为了在nums数组中找到和为P的子集的数量,这就是为什么我们计算left=(sum+target)/2并以此为基础进行动态规划的原因。

因此,动态规划解决方案基于这种转换,使用dp数组来逐步构建达到目标子集和的方法数量,最终解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

命运从未公平

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值