您需要找到列表中所有能给出目标值的金额组合。
列表长度nums- 最多 30 个元素。该列表可以包含从 1 到 9,999,999 的数字。目标值target可以是 1 至 9,999,999。
为了解决这个问题,必须删除重复项。对列表进行排序只需进行一次,不会浪费资源。
def find_combination_2(nums: list, target):
result = []
nums = list(set(nums)) # Убираем дубликаты
nums.sort() # Сортируем список
for r in range(1, len(nums) + 1):
print(r)
for combo in combinations(nums, r):
current_sum = sum(combo)
if current_sum == target:
result.append(list(combo))
elif current_sum > target:
print(current_sum)
break # Выходим из цикла, если сумма превышает целевое значение
return result
不使用多线程如何加速函数?
让我们尝试一下这个(类似于中间相遇算法):
我们将列表分成两部分,然后为每个部分生成所有可能的总和,包括空的总和。每部分的数量不会超过 32768 个。
然后,对于左边的每个和,我们在右边寻找补数。如果有,那么我们将生成所有组合对。
为了省钱,我们将组合存储为二进制数,其中单个位对应于相应列表中的索引(老实说,这是防止交叉的实验的遗留问题,但我没有重新设计它来存储数字本身)
示例包括捕获长度为 30 的列表中的随机数组合,这会产生相对较长的结果。工作迅速。
对于小目标值的求和问题也可以通过与成比例的时间动态规划来解决
target*len(nums)),这里还需要时间来存储和检查难以估计的中间组合,以及target中间组合的空间+存储。因为
那么我想提供我的解决方案。数字列表被分为两半,具体如何划分并不重要。对于每一半,构建一个字典<sum> → <具有此总和的术语元组列表>。我们查阅第一本字典,在第二本字典中找到了额外的金额。如果找到,我们将打印两个词典中的所有术语组合。速度与激情(就内存而言):
对以前的答案进行了小幅改进,或者更确切地说,渐近地进行了显着改进,但对于 Python 而言,改进并不大,因为使用
int和相对便宜的操作很昂贵sum()(我对@Stanislav Volodarskiy 的答案大约list有 -20..50%)。find_combination_7()如果我们按照格雷码的顺序生成组合,那么每个后续组合的和都与前一个组合相差一个元素。
测试(不删除重复项):
注意:如果您想使用此解决方案,请先阅读最后一节。
错误
无法删除重复项。该调用
find_combination_2([1, 1], 2)必须返回一个解决方案。如果删除重复项,那么根本就没有解决方案。另一方面,如果调用find_combination_2([1, 1, 1], 1)返回三个相同的解决方案(来自的三个不同的单位),那就不好了nums。所以我认为应该删除重复的解决方案(而不是原始数字)。您的函数未找到所有组合。一切都是因为
break结局。对于nums = [1, 2, 3, 4, 5]和,target = 5它将返回两种组合:[5],[1, 4]。如果删除break,将会发现另一个答案:[2, 3]。表现
我测试了没有的选项
break。在示例中nums = list(range(1, n + 1)),target = n。数字没有重复,第一个缺失并不影响答案。每增加
n一个单位,操作时间就会增加约两倍。如果您理解要整理2n个项的组合,那么这是很自然的。对于n = 30,我的计算机花了一分四十秒来找到答案。搜索可能会花费很长时间。在当前的设计中,用户直到整个搜索完成才会看到结果。你坐在屏幕前,不知道还要等多久。将函数转换为生成器将减轻这个缺点:用户将在解决方案可用时立即获得解决方案。
递归和搜索:
此函数在三分五十秒内找出三十个术语的所有组合。这意味着它花费230秒完成你的函数在100秒内完成的工作。这是一种优化。
但是此代码可以进行优化:我们将检查当前总数尚未超过目标总数,以及剩余项是否足以达到目标总数:
并且该函数可以在大约0.01秒内找到所有组合,也就是说,比不使用不等式检查的搜索快一万倍。
但这还不是极限。重点在于,条件搜索的速度受 中术语的顺序影响
nums。先处理大额条款会更有利可图。然后条件将尽早切断不成功的和:在这种形式下,函数可以以十倍的速度完成所有答案:仅用0.001秒。
我将添加一个选项,在我看来,它可以正确处理重复的术语。原则上,它们都是一样的:使用切分进行递归搜索,但术语是预先分组的:
自我批评
我选择用来展示递归修剪搜索的好处的例子很好地说明了它的好处:“加速一万倍”听起来不错。下面是另一个例子,表明有时所有的加速度都不值得:
在这个例子中,大多数优化都变成了失败。两分钟讲三十个词。我们又回到了原点,甚至更加糟糕。经验是艰难错误之子。