leetcode
leetcode 451 ~ 500
目标和

目标和

难度:

标签:

题目描述

给你一个非负整数数组 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

代码结果

运行时间: 41 ms, 内存: 16.1 MB


/*
题目思路:
可以用Java Stream来实现。我们将数组元素转换成其正负两种形式的数组,然后通过flatMap操作来合并两种可能性。
然后,使用collect(Collectors.toList())收集所有可能的组合。最后,通过stream().filter()来筛选出所有满足条件的组合。
*/
 
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
 
public class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        List<Integer> sums = Arrays.stream(nums)
                .boxed()
                .flatMap(num -> Arrays.asList(num, -num).stream())
                .collect(Collectors.toList());
 
        return (int) findCombinations(sums, 0, 0, target);
    }
 
    private long findCombinations(List<Integer> nums, int index, int currentSum, int target) {
        if (index == nums.size() / 2) {
            return currentSum == target ? 1 : 0;
        }
        return findCombinations(nums, index + 1, currentSum + nums.get(index * 2), target)
                + findCombinations(nums, index + 1, currentSum + nums.get(index * 2 + 1), target);
    }
}
 

解释

方法:

这个题解使用动态规划的思路来解决目标和问题。首先计算数组nums的总和total_sum,如果目标值target的绝对值大于总和或者(target + total_sum)不能被2整除,则无解返回0。否则,将问题转化为在nums中选择一些数,使得它们的和等于(target + total_sum) / 2。创建一个动态规划数组dp,其中dp[i]表示选择nums中的数,使得它们的和等于i的方案数。初始化dp[0]=1,表示当目标和为0时,只有一种方案,即什么都不选。然后遍历nums中的每个数num,对于每个可能的和j,更新dp[j] += dp[j-num],表示在之前的方案数的基础上,加上选择当前数num得到和为j的方案数。最终返回dp[target_sum],即达到目标和的方案总数。

时间复杂度:

O(n * target_sum)

空间复杂度:

O(target_sum)

代码细节讲解

🦆
如何确定(target + total_sum) % 2 == 1时题目无解?这个条件是怎样得出的?
这个条件基于将原问题转化为一个子集和问题的数学推导。原问题要求通过添加正负号使得数组nums的元素之和等于target。将所有添加了正号的元素和记为P,负号的元素和记为N,那么P - N = target。同时又有P + N = total_sum。联立这两个方程,解得P = (target + total_sum) / 2。由于P是一组元素的和,必须是非负整数。因此,如果target + total_sum是奇数,那么P无法为整数,从而无解。
🦆
动态规划数组dp的每个元素代表了什么意义?为什么初始化dp[0]为1?
在这个动态规划问题中,dp[i]代表从数组nums中选取若干个元素,其和为i的方案数。初始化dp[0]=1是因为和为0的方案数恰好有一种,即不选择任何元素。这个初始化是方案计数的基础,使得在后续的动态规划计算中,每次考虑添加新元素到已有子集中时,都可以正确计数。
🦆
为什么在更新dp数组时需要从target_sum开始向下遍历到num-1,而不是从0向上遍历?
在更新dp数组时,如果从0开始向上遍历到target_sum,那么在计算dp[j]时,dp[j-num]可能已经被更新过,这将导致重复计算某些方案。例如,如果num可以被多次累加计入同一个dp[j]中,就会错误地统计多种方案。而从target_sum向下遍历到num-1可以确保在更新dp[j]时,dp[j-num]尚未被本轮循环更新,从而避免了重复计算,确保每个元素在每个目标和中只被计算一次。
🦆
当nums数组中包含0时,这种动态规划方法是否需要做特殊处理?如果需要,如何处理?
当nums中包含0时,需要特殊处理,因为选择0的次数可以是任意多次(包括0次)。这种情况下,对于任何已经存在的方案数dp[i],选择0的方案可以增加新的组合方式。具体来说,你可以将每个dp[i]乘以2的零的个数次方(因为每个0都可以选择或不选择),从而正确计算出包含0的情况下,达到每个i的方案数。这一点需要在动态规划迭代过程中额外考虑。

相关问题

给表达式添加运算符

给定一个仅包含数字 0-9 的字符串 num 和一个目标值整数 target ,在 num 的数字之间添加 二元 运算符(不是一元)+- 或 * ,返回 所有 能够得到 target 的表达式。

注意,返回表达式中的操作数 不应该 包含前导零。

 

示例 1:

输入: num = "123", target = 6
输出: ["1+2+3", "1*2*3"] 
解释: “1*2*3” 和 “1+2+3” 的值都是6。

示例 2:

输入: num = "232", target = 8
输出: ["2*3+2", "2+3*2"]
解释: “2*3+2” 和 “2+3*2” 的值都是8。

示例 3:

输入: num = "3456237490", target = 9191
输出: []
解释: 表达式 “3456237490” 无法得到 9191 。

 

提示:

  • 1 <= num.length <= 10
  • num 仅含数字
  • -231 <= target <= 231 - 1