如何搜索数字组合(优化速度)?

时间:2017-03-08 15:30:23

标签: python

我试图寻找具有1,3,5,7 in的7位数字组合的数量(或更多,实际上需要它工作10,但用7测试更快)它。尝试了一些不同的方法,比如使用

combinations = 0
for combination in itertools.product(xrange(10), repeat=7):
    if all(x in combination for x in (1,3,5,7)):
        combinations += 1

但是,如果1不在列表中,那么下一个方法的速度大约要快4倍,因为它不会找到3,5,7。

combinations = 0

for combination in itertools.product(xrange(10), repeat=7):
    if 1 in combination:
        if 3 in combination:
            if 5 in combination:
                if 7 in combination:
                    combinations += 1

我确信有更多的切割方法可以通过numpy或类似的东西来实现这个结果,但我无法弄清楚。

感谢您的反馈

1 个答案:

答案 0 :(得分:2)

问题是找到包含所有数字1,3,5,7的k位数字。

这个答案包含许多解决方案,其复杂性和算法效率都在提高。最后,我们能够在几分之一秒内计算出巨大k的解,例如10 ^ 12,模数为大素数。

最后一节包含的测试可以很好地证明所有实现都是正确的。

蛮力:O(k10 ^ k)时间,O(k)空间

我们将使用这种缓慢的方法来测试更优化的代码版本:

def contains_1357(i):
    i = str(i)
    return all(x in i for x in '1357')

def combos_slow(k):
    return sum(contains_1357(i) for i in xrange(10 ** k))

计数:O(k ^ 4)时间,O(k)空间

最简单的中等效率方法是计算。一种方法是计算所有k位数字,其中四个特殊数字的第一次出现在数字a,b,c,d处。

给定a,b,c,d,a之前的数字必须为0,2,4,6,8,9,数字a必须是[1,3,5,7]中的一个, a和b之间的数字必须与数字a或任何安全数字相同,数字b必须是[1,3,5,7]中的一个,与a中的数字不同,等等。

对所有可能的a,b,c,d求和得出结果。像这样:

import itertools

def combos0(k):
    S = 0
    for a, b, c, d in itertools.combinations(range(k), 4):
        S += 6 ** a * 4 * 7**(b-a-1) * 3 * 8**(c-b-1) * 2 * 9**(d-c-1) * 10**(k-d-1)
    return S

动态编程:O(k)时间,O(k)然后是O(1)空间

你可以通过动态编程更有效地解决这个问题:让c [j] [i]是包含(1,3,5,7)正好j个不同数字的i位数字。

然后c满足这些递归关系:

c[0][0] = 1
c[j][0] = 0 for j > 0
c[0][i] = 6 * c[0][i-1] for i > 0
c[j][i] = (6+j)c[j][i-1] + (5-j)c[j-1][i-1] for i, j > 0

复发关系的最后一行是最难理解的。第一部分(6+j)c[j][i-1]表示您可以从i包含{{1}的数字编号中创建包含j数字1,3,5,7的i-1位数字数字1,3,5,7的数字,并添加一个额外的数字,即0,2,4,6,8,9或您已经获得的任何数字。同样,第二部分j表示您可以使用(5-j)c[j-1][i-1]个数字,其中包含数字1,3,5,7的i-1,并将其设为j-1 - 位数包含i个特殊数字的数字,通过添加您尚未使用过的数字之一。其中j

这导致使用动态编程的这个O(k)解决方案:

5-j

我们可以打印组合(10):

def combos(k):
    c = [[0] * (k + 1) for _ in xrange(5)]
    c[0][0] = 1
    for i in xrange(1, k+1):
        c[0][i] = 6 * c[0][i-1]
        for j in xrange(1, 5):
            c[j][i] = (6 + j) * c[j][i-1] + (5-j) * c[j-1][i-1]
    return c[4][k]

这给出了这个输出:

print 'combos(10) =', combos(10)

上面的解决方案已经足够快,可以在几分之一秒内计算combos(10) = 1425878520 。但是,通过观察combos(10000)的值仅依赖于表中的前一列,可以稍微优化DP解决方案以使用O(1)而不是O(k)空间。稍加注意(为了确保我们在重新使用之前不会覆盖它们),我们可以编写如下代码:

c

矩阵功率:O(log k)时间,O(1)空间。

最终,通过将递归关系表示为逐个矩阵的乘法,并通过平方使用取幂,可以将结果得到O(log k)时间和O(1)空间。这使得即使对于大量k(此处为组合(10 ^ 12)模2 ^ 31-1)也可以计算def combos2(k): c = [1, 0, 0, 0, 0] for _ in xrange(k): for j in xrange(4, 0, -1): c[j] = (6+j)*c[j] + (5-j)*c[j-1] c[0] *= 6 return c[4] 。看起来像这样:

combos(k) modulo X

鉴于你原来的代码在def mat_vec(M, v, X): return [sum(M[i][j] * v[j] % X for j in xrange(5)) for i in xrange(5)] def mat_mul(M, N, X): return [[sum(M[i][j] * N[j][k] for j in xrange(5)) % X for k in xrange(5)] for i in xrange(5)] def mat_pow(M, k, X): r = [[i==j for i in xrange(5)] for j in xrange(5)] while k: if k % 2: r = mat_mul(r, M, X) M = mat_mul(M, M, X) k //= 2 return r def combos3(k, X): M = [[6, 0, 0, 0, 0], [4, 7, 0, 0, 0], [0, 3, 8, 0, 0], [0, 0, 2, 9, 0], [0, 0, 0, 1, 10]] return mat_vec(mat_pow(M, k, X), [1, 0, 0, 0, 0], X)[4] print combos3(10**12, (2**31) - 1) 上挣扎,这是一个很大的改进!

测试

我们可以相互测试每个函数(对于小值,可以k=10)。由于combos_slow有一个额外的arg,我们将它包装在一个函数中,该函数传递的模数保证大于结果。

combos3

这针对i&lt; 7测试针对def combos3p(k): return combos3(k, 10**k) for c in [combos0, combos, combos2, combos3p]: for i in xrange(40 if c == combos0 else 100): assert c(i) == (combos_slow if i < 7 else combos)(i) 的所有实现,针对7&lt; = i&lt; 7测试针对彼此的所有实现。 100(效率较低的combos_slow停止在40)。