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

版权声明:本文为博主原创文章,未经博主允许不得转载。有事联系:[email protected] https://blog.csdn.net/qq_17550379/article/details/82939713

给定一个非负整数数组,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, target)表示[0:i]这个区间内目标为target的方法数,那么我们很容易得到下面这个表达式

  • f(i, target)=f(i-1, target-nums[i])+f(i-1, target+nums[i])

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

class Solution:
    def findTargetSumWays(self, nums, S):
        """
        :type nums: List[int]
        :type S: int
        :rtype: int
        """
        return self._findTargetSumWays(nums, 0, S)

    def _findTargetSumWays(self, nums, index, target):
        if index == len(nums):
            if target == 0:
                return 1
            return 0

        return self._findTargetSumWays(nums, index + 1, target-nums[index])\
                + self._findTargetSumWays(nums, index + 1, target+nums[index])

同样对于这种问题我们都可以通过记忆化搜索的方式去解决。这里要注意的细节就是,我们要记忆两种状态分别是index & target

class Solution:
    def findTargetSumWays(self, nums, S):
        """
        :type nums: List[int]
        :type S: int
        :rtype: int
        """
        mem = dict()
        return self._findTargetSumWays(nums, 0, S, mem)

    def _findTargetSumWays(self, nums, index, target, mem):
        if index == len(nums):
            if target == 0:
                return 1
            return 0

        tmp = "{},{}".format(index, target)
        if tmp in mem:
            return mem[tmp]

        mem[tmp] = self._findTargetSumWays(nums, index + 1, target-nums[index], mem)\
                    + self._findTargetSumWays(nums, index + 1, target+nums[index], mem)

        return mem[tmp]

这个问题最简洁的思路时通过动态规划来解。这实际上是一个背包问题,在背包问题中,我们要考虑物品放还是不放,而在这个问题中我们要考虑是加上一个数还是减去一个数。此时的背包的大小应该可以容纳[-sum(nums),sum(nums)]这个区间的所有数,而我们有len(nums)个元素,所以我们最后需要一个(len(nums)+1)(2*sum_nums + 1)大小的数组用来存储状态。传统背包问题可以用一个数组解决啊?着我们后面再说怎么用一维数组解决这个问题。接下来的过程很清晰,无非就是加上还是减去一个数的问题

  • mem[i][j] = mem[i-1][j+nums[i-1]] while j + nums[i-1] < 2*sum_nums + 1
  • mem[i][j] = mem[i-1][j-nums[i-1]] while j - nums[i-1] >= 0

最终代码如下

class Solution:
    def findTargetSumWays(self, nums, S):
        """
        :type nums: List[int]
        :type S: int
        :rtype: 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]      

我们怎么通过一维数组解决这个问题呢?实际上这个问题是之前Leetcode 416:分割等和子集(最详细的解法!!!) 提高,我们这里的问题同样可以理解为将nums拆分为P&N两个子集(P做加法,N做减法),那么我们的问题就变成了sum(P)-sum(N)=target也就是2*sum(P)=target+sum(nums),也就是说target+sum(nums)必须是一个偶数,这是一个非常重要的结论。我们通过这种思想结合动态规划,就可以写出下面的代码

class Solution:
    def findTargetSumWays(self, nums, S):
        """
        :type nums: List[int]
        :type S: int
        :rtype: 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

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

猜你喜欢

转载自blog.csdn.net/qq_17550379/article/details/82939713