【问题标题】:Solution Performance (DP, hashtable) for Partition Equal Subset Sum分区相等子集和的解决方案性能(DP,哈希表)
【发布时间】:2019-06-21 01:36:34
【问题描述】:

我知道在 stackoverflow 中已经提出了一些相关问题。然而,这个问题更多地与三种方法之间的性能差异有关。

问题是:给定一个仅包含正整数的非空数组,求该数组是否可以分成两个子集,使得两个子集中的元素之和相等。 https://leetcode.com/problems/partition-equal-subset-sum/

即 [1, 5, 11, 5] = 真,[1, 5, 9] = 假

通过解决这个问题,我尝试了3种方法:

  • 方法 1:动态规划。自上而下递归+记忆(结果:超过时间限制):

    def canPartition(nums):
        total, n = sum(nums), len(nums)
        if total & 1 == 1: return False
        half = total >> 1
        mem = [[0 for _ in range(half)] for _ in range(n)]
        def dp(n, half, mem):
            if half == 0: return True
            if n == -1: return False
            if mem[n - 1][half - 1]: return mem[n - 1][half - 1]
            mem[n - 1][half - 1] = dp(n - 1, half, mem) or dp(n - 1, half - nums[n - 1], mem)
            return mem[n - 1][half - 1]
        return dp(n - 1, half, mem)
    
  • 方法 2:动态规划。自下而上。 (结果:2208 毫秒 接受):

    def canPartition(self, nums):
        total, n = sum(nums), len(nums)
        if total & 1 == 1: return False
        half = total >> 1
        matrix = [[0 for _ in range(half + 1)] for _ in range(n)]
        for i in range(n):
            for j in range(1, half + 1):
                if i == 0: 
                    if j >= nums[i]: matrix[i][j] = nums[i]
                    else: matrix[i][j] = 0
                else:
                    if j >= nums[i]:
                        matrix[i][j] = max(matrix[i - 1][j], nums[i] + matrix[i - 1][j - nums[i]])
                    else: matrix[i][j] = matrix[i - 1][j]
                if matrix[i][j] == half: return True
        return False
    
  • 方法3:哈希表(字典)。结果(172 毫秒 接受):

    def canPartition(self, nums):
        total = sum(nums)
        if total & 1 == 0:
            half = total >> 1
            cur = {0}
            for number in nums:
                cur |= { number + x for x in cur} # update the dictionary (hashtable) if key doesn't exist
                if half in cur: return True
        return False
    

关于时间复杂度的上述 3 种方法,我真的不明白两件事:

  • 我希望方法 1方法 2 应该有相同的结果。两者都使用表格(矩阵)来记录计算状态,但为什么自底向上的方法更快?
  • 我不知道为什么方法 3 比其他方法快得多。注意:乍一看,方法3似乎是2的N次方方法,但它是使用字典丢弃重复值,所以时间复杂度应该是T(n * half)

【问题讨论】:

  • cur |= { number + x for x in cur} 这是一个非常 Python 的范例,它到底在做什么?
  • 如果键不存在更新字典(哈希表)。
  • 1 和 2. 这是背包问题的变体,1. 的时间复杂度不是 nW 而是 2^n,在 python 中使用递归是很糟糕的,我相信 leetcode 会适应的,无论如何.对于 2. 时间复杂度是 nW,对于 3. 我需要做更多的工作才能找到预期的运行时间。
  • is { number + x for x in cur} 联合?还是整个过程只是一个简单的插入?

标签: python algorithm hashtable dynamic-programming


【解决方案1】:

我对方法 1 和其他方法之间的区别的猜测是,由于递归,方法 1 需要生成更多的堆栈帧,这比仅分配矩阵和迭代条件花费更多的系统资源。但如果我是你,我会尝试使用某种进程和内存分析器来更好地确定和确认正在发生的事情。方法 1 分配一个取决于范围的矩阵,但该算法实际上将迭代次数限制为可能要少得多,因为下一个函数调用会跳转到减去数组元素的总和,而不是结合所有可能性。

方法 3 仅取决于输入元素的数量和可以生成的总和的数量。在每次迭代中,它将输入中的当前数字添加到所有先前可实现的数字中,仅将新数字添加到该列表中。例如,给定列表[50000, 50000, 50000],方法 3 将迭代最多三个和:50000、100000 和 150000。但由于它取决于范围,因此方法 2 将迭代至少 75000 * 3 次!

给定列表 [50000, 50000, 50000],方法 1、2 和 3 生成以下迭代次数:15、225000 和 6。

【讨论】:

    【解决方案2】:

    你是对的,方法1)和3)具有相同的时间复杂度,方法2是背包的DP版本(0/1),方法1是分支绑定版本。您可以通过任何背包启发式修剪树来改进方法一,但优化必须严格,例如如果现有的总和和 K 级剩余元素的总和小于一半,则跳过它。这种方法 1) 可以比 3) 具有更好的计算复杂度。

    为什么方法 1) 和 3) 的运行时间不同,

    [某种程度上]

    这更多地与python中字典的实现有关。字典是由 Python 解释器原生实现的,对它们的任何操作都将比任何需要首先解释且更频繁的操作要快。此外,函数调用在 python 中具有更高的开销,它们是对象。所以调用一个不是简单的bump up stack和jmp/call操作。

    [在很大程度上]

    另一个需要考虑的方面是第三种方法的时间复杂度。对于方法 3,时间复杂度呈指数增长的唯一方法是,每次迭代都会插入与当前迭代字典中的元素一样多的元素。

      cur |= { number + x for x in cur}
    

    上面的行应该是 |cur| 的两倍。

    我认为像这样的系列是可能的,

    s = {k,K2,K3, ..., kn, (>Kn+1)}

    (其中 K 是素数> 2) 为方法 3 给出 2n 的最坏情况时间。尚不确定平均预期时间复杂度是多少。

    【讨论】:

    • 在我看来,Python 的字典实现在这里不太可能是一个重要的促成因素。有没有办法在测试中证明这一点? (另外,在我看来,只有方法 3 是使用字典。)
    • 很公平,在您看来,有什么令人信服的方法来证明这一点?我还将重新查看正态分布的预期运行时间 3,它可能会少得多 2^n。
    • 不确定是否可以在这里对字典实现进行令人信服的测试,因为在我看来这三种算法是不同的。一个令人信服的测试将使用类似的算法和输入,一次使用 Python 字典,一次使用另一个数据结构来处理相同的记录。
    • 感谢您指出分支和界限,我没有注意到方法 1 与 2 不同。(修改了我的答案。)
    • 对字典和函数的一个令人信服的测试也可能是测试完全相同的三种算法并使用不同的编程语言输入。
    猜你喜欢
    • 2011-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-05
    • 2012-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多