Leetcode 494:目标和(最详细的解法!!!)

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

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

示例 1:

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

注意:

  1. 数组的长度不会超过20,并且数组中的值全为正数。
  2. 初始的数组的和不会超过1000。
  3. 保证返回的最终结果为32位整数。

解题思路

这个问题非常简单,我们首先想到的是通过回溯法解决这个问题。定义函数 f ( i , t a r g e t ) f(i, target) f(i,target)表示[0:i]这个区间内目标为target的方法数,那么

  • f ( i , t a r g e t ) = f ( i − 1 , t a r g e t − n u m s [ i ] ) + f ( i − 1 , t a r g e t + n u m s [ i ] ) f(i, target)=f(i-1, target-nums[i])+f(i-1, target+nums[i]) f(i,target)=f(i1,targetnums[i])+f(i1,target+nums[i])

边界条件就是i==len(nums)时,我们要判断target == 0,如果是的话返回1,否则0

from functools import lru_cache
class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        @lru_cache(None)
        def dfs(index, target):
            if index == len(nums):
                if target == 0:
                    return 1
                return 0
            
            return dfs(index + 1, target - nums[index]) + \
                dfs(index + 1, target + nums[index])
        
        return dfs(0, S)

这个问题最简洁的思路是通过动态规划来解。这实际上是一个背包问题,在背包问题中,我们要考虑物品放还是不放,而在这个问题中我们要考虑是加上一个数还是减去一个数。此时的背包的大小应该可以容纳[-sum(nums),sum(nums)]这个区间的所有数,而我们有len(nums)个元素,所以最后需要一个(len(nums)+1)(2*sum_nums+1)大小的数组用来存储状态。

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        sum_nums = sum(nums)
        if sum_nums < S or -sum_nums > S:
            return 0
        
        len_nums = len(nums)
        mem = [[0]*(2*sum_nums + 1) for _ in range(len_nums+1)]
        mem[0][sum_nums] = 1
        for i in range(1, len_nums + 1):
            for j in range(2*sum_nums + 1):
                if j + nums[i - 1] < 2*sum_nums + 1:
                    mem[i][j] += mem[i - 1][j + nums[i - 1]]
                if j - nums[i - 1] >= 0:
                    mem[i][j] += mem[i - 1][j - nums[i - 1]]
                    
        return mem[len_nums][sum_nums + S]      

空间复杂度还可以继续优化,怎么做呢?我们这里的问题可以理解为将nums拆分为P&N两个子集(P做加法,N做减法),那么问题就变成了sum(P)-sum(N)=target也就是2*sum(P)=target+sum(nums),也就是说target+sum(nums)必须是一个偶数,这是一个非常重要的结论。然后剩下的问题就是计算01背包问题的方案个数了。

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        sum_nums = sum(nums)
        if sum_nums < S or (S + sum_nums)%2 != 0:
            return 0

        target = (S + sum_nums) >> 1
        mem = [0]*(target + 1)
        mem[0] = 1
        for num in nums:
            for i in range(target, num-1, -1):
                mem[i] += mem[i - num]
        return mem[target]

数学与程序的完美融合,这也是很多黑客喜欢的编程方式。

reference:

https://leetcode.com/problems/target-sum/discuss/97334/Java-(15-ms)-C+±(3-ms)-O(ns)-iterative-DP-solution-using-subset-sum-with-explanation

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值