繁体   English   中英

用于计算数字和的组合数得出固定结果的算法难题

[英]algorithmic puzzle for calculating the number of combinations of numbers sum to a fixed result

自昨晚以来,这是我想到的一个难题。 我想出了一个解决方案,但是效率不高,所以我想看看是否有更好的主意。

难题是这样的:

给定正整数N和T,您将需要:

for i in [1, T], A[i] from { -1, 0, 1 }, such that SUM(A) == N

additionally, the prefix sum of A shall be [0, N], while when the prefix sum PSUM[A, t] == N, it's necessary to have for i in [t + 1, T], A[i] == 0

here prefix sum PSUM is defined to be: PSUM[A, t] = SUM(A[i] for i in [1, t])

the puzzle asks how many such A's exist given fixed N and T

例如,当N = 2T = 4 ,遵循A的工作:

1 1 0 0
1 -1 1 1
0 1 1 0

但以下不要:

-1 1 1 1  # prefix sum -1
1 1 -1 1  # non-0 following a prefix sum == N
1 1 1 -1  # prefix sum > N

当给定N为expect且给定A实例为seq时,以下python代码可以验证该规则(某些人可能比阅读文字描述更容易阅读代码):

def verify(expect, seq):
    s = 0
    for j, i in enumerate(seq):
        s += i
        if s < 0:
            return False
        if s == expect:
            break
    else:
        return s == expect
    for k in range(j + 1, len(seq)):
        if seq[k] != 0:
            return False
    return True

我已经编写了解决方案的代码,但是太慢了。 以下是我的:

我将问题分解为两部分,一部分不包含-1 (仅{0, 1} ,另一部分带有-1

因此,如果SOLVE(N, T)是正确答案,则定义一个函数SOLVE'(N, T, B) ,其中正B允许我将前缀和扩展为[-B, N]的区间的[0, N]

所以实际上SOLVE(N, T) == SOLVE'(N, T, 0)

所以我很快意识到解决方案实际上是:

  1. 具有A的前缀是一些有效的{0, 1}组合,长度为l ,其中o 1 s
  2. 在位置l + 1 ,我开始加1个或多个-1 s并使用B来跟踪数字。 最大值将为B + o或取决于A剩余的插槽数,以较小者为准。
  3. 递归调用SOLVE'(N, T, B)

在前面的N = 2, T = 4示例中,在一种搜索情况下,我将执行以下操作:

  1. 假设A的前缀为[1],则A = [1, -, -, -]
  2. 开始添加-1 在这里,我将仅添加一个: A = [1, -1, -, -]
  3. 递归调用SOLVE' ,这里我将调用SOLVE'(2, 2, 0)解决最后两个问题。 在这里它将仅返回[1, 1] 然后组合之一会产生[1, -1, 1, 1]

但是这个算法太慢了。

我想知道如何优化它或以其他方式查看可以提高性能的问题(我只需要这个想法,而不是暗示)

编辑:

一些示例将是:

T N RESOLVE(N, T)
3 2 3
4 2 7
5 2 15
6 2 31
7 2 63
8 2 127
9 2 255
10 2 511
11 2 1023
12 2 2047
13 2 4095
3 3 1
4 3 4
5 3 12
6 3 32
7 3 81
8 3 200
9 3 488
10 3 1184
11 3 2865
12 3 6924
13 3 16724
4 4 1
5 4 5
6 4 18

指数时间解决方案通常如下(在python中):

import itertools
choices = [-1, 0, 1]
print len([l for l in itertools.product(*([choices] * t)) if verify(n, l)])

一个观察结果:假设n至少为1 ,则您所陈述问题的每个解决方案都以[1, 0, ..., 0]形式结束:即,单个1后跟0或多个0 s。 解决方案在该点之前的部分是一个完全位于[0, n-1] ,从0开始,在n-1结束且步长少于t步行。

因此,您可以将原始问题简化为一个简单的问题,即确定[0, n]中有多少t步走,从0开始到n (每个步可以是0+1-1 ,和以前一样)。

以下代码解决了较简单的问题。 它使用lru_cache装饰器来缓存中间结果。 可以在Python 3的标准库中找到,也可以从Python 2下载食谱

from functools import lru_cache

@lru_cache()
def walks(k, n, t):
    """
    Return the number of length-t walks in [0, n]
    that start at 0 and end at k. Each step
    in the walk adds -1, 0 or 1 to the current total.

    Inputs should satisfy 0 <= k <= n and 0 <= t.
    """
    if t == 0:
        # If no steps allowed, we can only get to 0,
        # and then only in one way.
        return k == 0
    else:
        # Count the walks ending in 0.
        total = walks(k, n, t-1)
        if 0 < k:
            # ... plus the walks ending in 1.
            total += walks(k-1, n, t-1)
        if k < n:
            # ... plus the walks ending in -1.
            total += walks(k+1, n, t-1)
        return total

现在我们可以使用此功能解决您的问题。

def solve(n, t):
    """
    Find number of solutions to the original problem.
    """
    # All solutions stick at n once they get there.
    # Therefore it's enough to find all walks
    # that lie in [0, n-1] and take us to n-1 in
    # fewer than t steps.
    return sum(walks(n-1, n-1, i) for i in range(t))

我的机器上的solve(10, 100)结果和时间:

In [1]: solve(10, 100)
Out[1]: 250639233987229485923025924628548154758061157

In [2]: %timeit solve(10, 100)
1000 loops, best of 3: 964 µs per loop

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM