只有 2^20(略超过一百万)个长度为 20 的二进制向量,而不是不可行的 20!。使用应该能够在不到一秒的时间内暴力破解,特别是如果您使用 Gray Code 这将允许您在一个步骤中从一个候选总和传递到另一个候选总和(例如从 a + b - c -d 到 @ 987654324@加2*d即可。
如果y 变得更大,@MikeWise 的出色分支和绑定想法会很好。生成以0 为根节点的树。给它-a1 和+a1 的孩子。然后通过添加和减去a2 等来创建 4 个孙子。如果您从目标 x 得到的距离超过剩余 ai 的总和 - 您可以修剪该分支。在最坏的情况下,这可能比基于格雷码的暴力破解稍差(因为您需要在每个节点上进行更多的处理),但在最好的情况下,您可能能够消除大多数可能性。
编辑时:这是一些 Python 代码。首先我定义了一个生成器,给定一个整数n,依次返回需要翻转的位位置以逐步通过格雷码:
def grayBit(n):
code = [0]*n
odd = True
done = False
while not done:
if odd:
code[0] = 1 - code[0] #flip bit
odd = False
yield 0
else:
i = code.index(1)
if i == n-1:
done = True
else:
code[i+1] = 1 - code[i+1]
odd = True
yield i+1
(这使用了我多年前在 Stanton 和 White 的优秀著作“Constructive Combinatorics”中学到的算法)。
然后——我用它来返回所有解决方案(作为由输入的数字列表组成的列表,并根据需要插入负号)。关键是我可以将当前的位翻转并加或减相应数字的两倍:
def signedSums(nums, target):
n = len(nums)
patterns = []
total = sum(nums)
pattern = [1]*n
if target == total: patterns.append([x*y for x,y in zip(nums,pattern)])
deltas = [2*i for i in nums]
for i in grayBit(n):
if pattern[i] == 1:
total -= deltas[i]
else:
total += deltas[i]
pattern[i] = -1 * pattern[i]
if target == total: patterns.append([x*y for x,y in zip(nums,pattern)])
return patterns
典型输出:
>>> signedSums([1,2,3,4,5,9],6)
[[1, -2, -3, -4, 5, 9], [1, 2, 3, -4, -5, 9], [-1, 2, -3, 4, -5, 9], [1, 2, 3, 4, 5, -9]]
评估只需要大约一秒钟:
>>> len(signedSums([i for i in range(1,21)],100))
2865
因此,有 2865 种方法可以对 1,2,..,20 范围内的整数进行加减运算,得到净和 100。
我假设a1 可以添加或减去(而不是仅添加,如果从字面上理解,这就是您的问题所暗示的)。请注意,如果您真的想坚持a1 是肯定发生的,那么您可以从x 中减去它,并将上述算法应用于列表的其余部分和调整后的目标。
最后,不难看出,如果您使用权重集{2*a1, 2*a2, 2*a3, .... 2*ay} 和目标总和为x + a1 + a2 + ... + ay 求解subset sub problem,那么选择的子集将完全对应于正迹象出现在原问题的解决方案中。因此,您的问题很容易简化为子集和问题,因此确定它是否有任何解决方案是 NP 完全的(并且 NP 很难将它们全部列出)。