你抓住了我这个有竞争力的程序员。
使用动态编程(记忆),我能够将运行时间降低到合理的水平。
首先,我们定义我们拥有的权重类型,以及我们想要达到的目标。
weights = [5, 10, 25, 35, 45]
targets = [30, 40, 45, 50, 55, 65, 70, 80]
接下来我们有主要的 DP 功能。walk
接受一个已使用权重的有序列表(最初为空)并pos
告诉我们我们已经考虑了哪些权重(最初为零)。walk
这减少了对from O(n!)
to的调用次数O(2^n)
。walk
也被记忆,进一步降低执行时间从O(2^n)
到O(n)
。
有一些基本情况,其中一些是为了性能而动态修改的:
pos >= len(weights)
如果 pos 大于权重的长度,我们已经检查了所有的权重,并且我们完成了递归。
len(used) > max(targets) / min(weights)
这是对要使用的权重数量的弱限制。如果有办法只使用最小的权重并且仍然通过最大的目标,我们知道我们已经检查了足够多的数字,并且这个分支是无用的。继续。
len(used) > bwnum
bwnum
到目前为止,最佳答案中使用的权重数量在哪里。由于这是我们的主要标准,当我们选择的权重超过时,我们可以停止递归bwnum
。这是一个很大的优化,假设我们很快找到任何有效的答案。
对于 和 两种情况a
,b
我们可以选择另一种权重pos
,也可以继续pos
前进。最好的(最短的,然后是最小的总和)被记忆并返回。由于有两种情况,我们有一个分支因子2
。
mem = {}
bwnum = len(weights)+1
def walk(used, pos):
k = (used, pos)
global bwnum, weights, targets
if pos >= len(weights) or len(used) > bwnum or len(used) > max(targets) / min(weights):
return used if valid(used) else (1e9,)*(bwnum+1)
if k not in mem:
a = walk(used + (weights[pos],), pos)
b = walk(used, pos + 1)
mem[k] = a if len(a) < len(b) or (len(a) == len(b) and sum(a) < sum(b)) else b
if valid(mem[k]):
bwnum = min(bwnum, len(mem[k]))
return mem[k]
然后我们需要一个验证器函数来检查给定的权重列表是否足以达到所有目标。这可以进一步优化,但速度非常快。我将 80% 的执行时间花在了这个函数上。
from itertools import combinations
vmem = {}
def valid(used):
if used not in vmem:
tmap = {}
for t in targets:
tmap[t] = 0
for le in range(1, len(used) + 1):
for c in combinations(used, le):
if sum(c) in tmap:
del tmap[sum(c)]
vmem[used] = len(tmap) == 0
return vmem[used]
最后,walk
使用空参数调用,并打印结果。
r = walk((), 0)
print r, len(r), sum(r)
哪个输出:
(5, 5, 10, 25, 35) 5 80
哦,顺便说一句,你的例子是正确的。谢谢。