【问题标题】:How to understand leetcode 494 Target Sum ( knapsack problem ) fastest python code using bit operation如何理解leetcode 494 Target Sum(背包问题)最快的使用位运算的python代码
【发布时间】:2021-01-09 05:00:49
【问题描述】:

问题描述如下 https://leetcode.com/problems/target-sum/

给定一个非负整数列表 a1、a2、...、an 和一个目标 S。现在您有 2 个符号 + 和 -。对于每个整数,您应该从 + 和 - 中选择一个作为其新符号。

找出有多少种分配符号的方法以使整数之和等于目标 S。

约束:

  • 给定数组的长度为正数,不会超过 20。
  • 给定数组中元素的总和不会超过 1000。
  • 保证您的输出答案适合 32 位整数。

我在 leetcode 提交详情 Accepted Solutions Runtime Distribution 中找到了这个提交

class Solution:
    def findTargetSumWays(self, nums, S):
        a = sum(nums) - S
        if a < 0 or a%2==1: return 0 
        S = [((1<<(i*21))+1) for i in nums]
        return reduce(lambda p,i:(p*i)&(1<<((a//2+1)*21))-1,S,1)>>(21*a//2)

简化reduce,就变成了

class Solution:
    def findTargetSumWays(self, nums, S):
        a = sum(nums) - S
        if a < 0 or a%2==1: return 0 
        auxarr = [((1<<(i*21))+1) for i in nums]
        ret=1
        for i in auxarr:
            ret= (ret*i)&(1<<((a//2+1)*21))-1
        return ret>>(21*a//2)

它将原始问题转换为另一个问题,即找到选择一些nums[i]且它们的总和为(sum(nums)-S)/2的选择数。

我知道如何用dp解决这样的背包问题,但是我看不懂上面的代码,我很好奇这样的代码是如何工作的,请帮助我。

# my dp code
class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        S=sum(nums)-S
        if S%2!=0 or S<0: return 0
        S//=2
        dp=[0]*(S+1)
        dp[0]=1
        for c in nums:
            for j in range(S,c-1,-1):
                dp[j]+=dp[j-c]
        return dp[S]

【问题讨论】:

  • 您可以将retauxarr 的元素视为0 和1 的列表。 ((1&lt;&lt;(i*21))+1) 将对应一个列表,其中位置 0 和 i*21 的元素为 1,其余为 0
  • 或者更好地作为 [0, 2^21) 范围内的整数列表而不是位

标签: python


【解决方案1】:

它似乎使用多项式的特征,其中您将由 (B^n+1) 形成的项相乘,其中 B 是 2 的幂,足以避免重叠。

所以,假设您有 3 个数字 (x,y,z) 要相加,它将计算:

(B^x + 1)(B^y + 1)(B^z + 1)

这些多项式的指数将在结果中相加

B^(x+y+z) + B^(x+z) + B^(y+z) + B^z + B^(x+y) + B^x + B^y + 1

因此,如果指数(即数字)的任何组合加起来相同的总数,则 B^total 出现的次数将是获得该总数的方法数。利用多项式的这一特性,我们将在结果中找到方法*B^total。如果路数与 B^(total+1) 的值不重叠,则可以使用掩码和整数除法提取。

例如,给定 4 个数字 h、i、j、k,乘积将产生 B 的总和,其乘方对应于 1 到 4 个数字相加的每个组合。因此,如果我们要求总 T,并且 h+i 和 j+k 等于 T。那么乘积将包含由 B^(h+i) + B^(j+k) 组成的 2*B^T .这对应于形成和 T 的两种方式。

鉴于每个数字(+ 或 -)有 2 种可能性,最多有 2^20 种可能的方式来组合它们。为了确保 B^x 的任何总和不与 B^(x+1) 重叠,为 B 选择 2^21 的值。

这就是为什么偏移数组(变量名 S 在这里是一个非常糟糕的选择)对于 nums 中的每个 n 由 (B^n+1) 组成,其中 B 是 2^21,所以 (2^21^n +1) ... (2^(21n)+1) ... (1&lt;&lt;(21*n))+1

为了能够使用多项式方法,需要将问题转换为缺席/存在问题。这是通过推理必须有一个通过相互抵消产生零和的数字组合来完成的,其余的为正并加起来为 S。因此,如果我们从数字总数中删除 S,则将是一个组合,加起来是剩余部分的一半 (a//2)。这将是我们要寻找的总数。

reduce 函数实现多项式乘积并应用掩码 ((1&lt;&lt;((a//2+1)*21))-1) 来切断 B 的任何超出 B^(a/2) 的幂。最终结果通过移位将B^(a/2)以下的部分截掉。

这会产生 B^(a/2) 的倍数,它对应于产生指数和(即数字和)的方式数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-17
    • 1970-01-01
    • 2020-05-01
    • 1970-01-01
    • 2021-08-14
    • 1970-01-01
    • 2011-04-23
    相关资源
    最近更新 更多