494. 目标和

494. 目标和

问题描述

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

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

示例:

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

提示:

数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum

解答

这题是在练习01背包找的题目,先入为主的就拿背包问题来做:

\[f(i, j) = f(i-1, j - num[i]) + f(i - 1, j + num[i]) \]

\(f(i, j )\)是状态转移方程,将i个物品放入空间为j的背包的次数,因为可以存在加减法,所以为上述式子,但是在计算\(f(i, j)\)\(f(i-1, j+num[i])\)是没有计算出来的。在查看别人的计算方法中,还想仍然有这样可以做出来的 ,但是这里就不考虑了。

正确且方便的方法是,先观察题目,如果相加的数字和为p,相减的数字和为m,那么可以得到

\[p + m = sum(nums) \\ p - m = S \\ \therefore p = \frac{sum(nums) + S}{2} \]

所以只需要在所有数字中,找到部分数字,让其和为 \(\frac{sum(nums) +S}{2}\),这才是一个典型的01背包的问题:

\[f(i, j) = f(i - 1, j) + f(i - 1, j - nums[i]) \]

\(f(i, j)\)表示前 i 个物品装入空间为 j 的背包可能的数目。

python 代码如下,这里需要注意的是,在初始化 dp 数组时,dp[0][0]为 1, 表示将 0 个物品放入 0 的背包的可能的次数为 1,并且其他行的第 0 个都是类似的情况。但下面就需要注意了,在传统的01背包中,第二个循环是从 1 开始的,但是这里因为可能的存在数字 0,所以我们必须从 0 开始。

from typing import List
class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        N = len(nums)
        nums = [0, ] + nums

        # 判断可能性
        p, r = divmod(sum(nums) + S, 2)
        if r != 0 or abs(S) > sum(nums):
            return 0

        # dp 数组
        dp = [[1] + [0] * p for _ in range(N + 1)]

        for i in range(1, N + 1):
            for j in range(p + 1):
                if j - nums[i] >= 0:
                    dp[i][j] = dp[i-1][j] + dp[i-1][j - nums[i]]
                else:
                    dp[i][j] = dp[i-1][j]

        return dp[-1][-1]

当然也可以进行空间的优化,注意第二个循环时反向的:

from typing import List
class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        N = len(nums)
        nums = [0, ] + nums

        # 判断可能性
        p, r = divmod(sum(nums) + S, 2)
        if r != 0 or abs(S) > sum(nums):
            return 0

        # dp 数组
        dp = [1] + [0] * p

        for i in range(1, N + 1):
            for j in range(p, 0-1, -1):
                if j - nums[i] >= 0:
                    dp[j] = dp[j] + dp[j - nums[i]]

        return dp[-1]

总结

  1. 观察题目,这样的题目可能能够推导出更加简单的公式。
  2. 注意01背包的初始化以及循环,针对不同的题目,可能会存在不同的情况。
posted @ 2021-03-14 21:00  丿小呆  阅读(97)  评论(0编辑  收藏  举报