[英]Trying to understand the time complexity of this dynamic recursive subset sum
# Returns true if there exists a subsequence of `A[0…n]` with the given sum
def subsetSum(A, n, k, lookup):
# return true if the sum becomes 0 (subset found)
if k == 0:
return True
# base case: no items left, or sum becomes negative
if n < 0 or k < 0:
return False
# construct a unique key from dynamic elements of the input
key = (n, k)
# if the subproblem is seen for the first time, solve it and
# store its result in a dictionary
if key not in lookup:
# Case 1. Include the current item `A[n]` in the subset and recur
# for the remaining items `n-1` with the decreased total `k-A[n]`
include = subsetSum(A, n - 1, k - A[n], lookup)
# Case 2. Exclude the current item `A[n]` from the subset and recur for
# the remaining items `n-1`
exclude = subsetSum(A, n - 1, k, lookup)
# assign true if we get subset by including or excluding the current item
lookup[key] = include or exclude
# return solution to the current subproblem
return lookup[key]
if __name__ == '__main__':
# Input: a set of items and a sum
A = [7, 3, 2, 5, 8]
k = 14
# create a dictionary to store solutions to subproblems
lookup = {}
if subsetSum(A, len(A) - 1, k, lookup):
print('Subsequence with the given sum exists')
else:
print('Subsequence with the given sum does not exist')
据说这个算法的复杂度是O(n * sum),但是我不明白如何或为什么; 有人能帮我吗? 可能是冗长的解释或递归关系,什么都可以
我能给出的最简单的解释是意识到当lookup[(n, k)]
有一个值时,它是 True 或 False 并指示A[:n+1]
的某个子集是否与k
相加。
想象一个简单的算法,它只是逐行填充查找的所有元素。
lookup[(0, i)]
(对于 0 ≤ i
≤ total
)只有两个元素为真, i = A[0]
和i = 0
,所有其他元素都是假的。
如果lookup[(0, i)]
为真或i ≥ A[1]
且lookup[(0, i - A[1])
为真,则lookup lookup[(1, i)]
(对于0 ≤ i
≤ total
)为真真的。 我可以通过使用A[i]
或不使用来达到总和i
,而且我已经计算了这两个。
... 如果lookup[(r, i)]
lookup[(r - 1, i)]
为真或i
total
i ≥ A[r]
并且lookup[(r - 1, i - A[r])
为真。
以这种方式填充这个表,很明显我们可以在len(A) * total
的时间内完全填充行0 ≤ row < len(A)
的查找表,因为线性填充每个元素。 我们的最终答案只是检查表中是否(len(A) - 1, sum)
True。
您的程序正在做完全相同的事情,但会根据需要计算lookup
条目的值。
抱歉提交了两个答案。 我想我想出了一个稍微简单的解释。
想象一下您的代码将三行放入if key not in lookup:
到单独的 function, calculateLookup(A, n, k, lookup)
中。 我将调用“为 n 和 k 调用n
和k
的特定值n
和k
的成本是调用calculateLookup
calculateLookup(A, n, k, loopup)
所花费的总时间,但不包括任何递归调用calculateLookup
。
关键的见解是,如上所述,对任何n
和k
调用calculateLookup()
的成本是 O(1)。 由于我们在成本中排除了递归调用,并且没有 for 循环,因此calculateLookup
的成本是仅执行几个测试的成本。
整个算法做固定量的工作,调用calculateLookup
,然后做少量的工作。 因此,在我们的代码中花费的时间与询问我们调用了多少次calculateLookup
相同?
现在我们回到之前的答案。 由于查找表,每次调用calculateLookup
时都会使用不同的(n, k)
值。 我们还知道,我们在每次调用calculateLookup
之前检查n
和k
的边界,因此1 ≤ k ≤ sum
和0 ≤ n ≤ len(A)
。 因此, calculateLookup
最多被调用(len(A) * sum)
次。
一般来说,对于这些使用 memoization/cacheing 的算法,最简单的做法是分别计算然后求和:
您提出的算法只是填满了lookup
缓存。 它以不寻常的顺序执行它,并且它没有填充表中的每个条目,但这就是它所做的全部。
代码会稍微快一点
lookup[key] = subsetSum(A, n - 1, k - A[n], lookup) or subsetSum(A, n - 1, k, lookup)
在最坏的情况下不会改变代码的 O(),但可以避免一些不必要的计算。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.