zoukankan      html  css  js  c++  java
  • 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 \ herefore 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背包的初始化以及循环,针对不同的题目,可能会存在不同的情况。
  • 相关阅读:
    5.对象创建型模式-原型PROTOTYPE
    4.对象创建型模式-工厂方法
    3.对象创建型模式-生成器
    一个小应用的dbcp和c3p0配置实例
    利用 java.lang.Runtime.addShutdownHook() 钩子程序,保证java程序安全退出
    初探maven插件机制
    【转载】Git push时重复输入用户名密码的问题
    【转载】 ERROR 1045 (28000): Access denied for user root@localhost (using password: NO)
    【转载】[Java]读取文件方法大全
    【原创】iframe与父页面之间,变量、方法互相调用
  • 原文地址:https://www.cnblogs.com/XD00/p/14534135.html
Copyright © 2011-2022 走看看