[英]Getting all subsets from subset sum problem on Python using Dynamic Programming
我正在尝试从元素列表中提取所有子集,这些元素的总和为某个值。
例子 -
尝试了不同的方法并获得了预期的输出,但在大量元素上,这需要大量时间。 这可以使用动态编程或任何其他技术进行优化。
方法一
def subset(array, num):
result = []
def find(arr, num, path=()):
if not arr:
return
if arr[0] == num:
result.append(path + (arr[0],))
else:
find(arr[1:], num - arr[0], path + (arr[0],))
find(arr[1:], num, path)
find(array, num)
return result
numbers = [2, 2, 1, 12, 15, 2, 3]
x = 7
subset(numbers,x)
方法二
def isSubsetSum(arr, subset, N, subsetSize, subsetSum, index , sum):
global flag
if (subsetSum == sum):
flag = 1
for i in range(0, subsetSize):
print(subset[i], end = " ")
print("")
else:
for i in range(index, N):
subset[subsetSize] = arr[i]
isSubsetSum(arr, subset, N, subsetSize + 1,
subsetSum + arr[i], i + 1, sum)
如果你想输出所有的子集,你不能比缓慢的 O(2^n) 复杂度做得更好,因为在最坏的情况下,输出的大小和时间复杂度受输出大小的限制(这是一个已知的 NP 完全问题)。 但是,如果您不想返回所有子集的列表,而只想返回一个布尔值,该值指示是否可以实现目标总和,或者只返回一个子集总和到目标(如果存在),则可以使用动态编程进行伪-多项式 O(nK) 时间解,其中 n 是元素数,K 是目标整数。
DP 方法涉及填写一个 (n+1) x (K+1) 表,表中条目对应的子问题为:
DP[i][k] = subset(A[i:], k) for 0 <= i <= n, 0 <= k <= K
也就是说,subset(A[i:], k) 询问,“我可以使用从索引 i 开始的 A 的后缀求和到(小)k 吗?” 一旦你填满了整个表格,整个问题的答案,subset(A[0:], K) 将在 DP[0][K]
基本情况适用于 i=n:它们表示如果您使用数组的空后缀,则不能求和除 0 以外的任何值
subset(A[n:], k>0) = False, subset(A[n:], k=0) = True
填表的递归情况是:
subset(A[i:], k) = subset(A[i+1:, k) OR (A[i] <= k AND subset(A[i+i:], k-A[i]))
这只是将以下想法联系起来,即您可以通过跳过该后缀的第一个元素并使用您在前一行中已有的答案(当第一个元素不在您的数组后缀中时)来使用当前数组后缀求和为 k ),或者通过在总和中使用A[i]
并检查是否可以在前一行中减少总和kA[i]
。 当然,只有当新元素本身不超过您的目标总和时,您才能使用它。
例如:subset(A[i:] = [3,4,1,6], k = 8) 会检查:我是否已经用前一个后缀 (A[i+1:] = [4,1 ,6])? 否。或者,我可以使用现在可用的 3 来求和为 8 吗? 也就是说,我可以用 [4,1,6] 求和 k = 8 - 3 = 5 吗? 是的。 因为至少有一个条件为真,所以我设置 DP[i][8] = True
因为所有的基本情况都是针对 i=n,而子集(A[i:], k) 的递推关系依赖于较小子问题子集(A[i+i:],...) 的答案,您从表的底部开始,其中 i = n,为每一行填写从 0 到 K 的每个 k 值,然后一直工作到第 i = 0 行,确保您有较小子问题的答案当你需要它们时。
def subsetSum(A: list[int], K: int) -> bool:
N = len(A)
DP = [[None] * (K+1) for x in range(N+1)]
DP[N] = [True if x == 0 else False for x in range(K+1)]
for i in range(N-1, -1, -1):
Ai = A[i]
DP[i] = [DP[i+1][k] or (Ai <=k and DP[i+1][k-Ai]) for k in range(0, K+1)]
# print result
print(f"A = {A}, K = {K}")
print('Ai,k:', *range(0,K+1), sep='\t')
for (i, row) in enumerate(DP): print(A[i] if i < N else None, *row, sep='\t')
print(f"DP[0][K] = {DP[0][K]}")
return DP[0][K]
subsetSum([1,4,3,5,6], 9)
如果您想在 bool 旁边返回一个实际可能的子集,指示是否可以创建一个子集,那么对于 DP 中的每个 True 标志,您还应该存储使您到达那里的前一行的 k 索引(它要么是当前 k 索引或 kA[i],取决于哪个表查找返回 True,这将指示是否使用了 A[i])。 然后在表填满后从 DP[0][K] 向后走,得到一个子集。 这使得代码更加混乱,但它绝对是可行的。 但是,您无法通过这种方式获得所有子集(至少不会再次增加时间复杂度),因为 DP 表会压缩信息。
这是复杂度为 O(n^2) 的问题的优化解决方案。
def get_subsets(data: list, target: int):
# initialize final result which is a list of all subsets summing up to target
subsets = []
# records the difference between the target value and a group of numbers
differences = {}
for number in data:
prospects = []
# iterate through every record in differences
for diff in differences:
# the number complements a record in differences, i.e. a desired subset is found
if number - diff == 0:
new_subset = [number] + differences[diff]
new_subset.sort()
if new_subset not in subsets:
subsets.append(new_subset)
# the number fell short to reach the target; add to prospect instead
elif number - diff < 0:
prospects.append((number, diff))
# update the differences record
for prospect in prospects:
new_diff = target - sum(differences[prospect[1]]) - prospect[0]
differences[new_diff] = differences[prospect[1]] + [prospect[0]]
differences[target - number] = [number]
return subsets
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.