算法难题,用于计算数字组合的数量总和与固定结果

时间:2016-01-10 18:38:19

标签: algorithm

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

难题是:

给出正整数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 = 4A之后的工作:

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

以下python代码可以验证此类规则,当N为expect且A的实例为seq时(有些人可能比阅读文字说明更容易阅读代码):

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]。 1}}

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

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

  1. 将A的前缀设为有效{0, 1}组合,其长度为l,其中包含o 1
  2. l + 1位置,我开始添加1个或多个-1并使用B来跟踪该数字。最大值为B + o或取决于A中剩余的广告位数,以较少者为准。
  3. 递归调用SOLVE'(N, T, B)
  4. 在上一个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]
    4. 但是这个算法太慢了。

      我想知道如何优化它或以任何不同的方式来看待可以提升性能的这个问题?(我只需要这个想法,而不是impl)

      编辑:

      一些样本将是:

      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)])
      

1 个答案:

答案 0 :(得分:1)

观察:假设n至少为1,您所述问题的每个解决方案都会以[1, 0, ..., 0]形式结束:即,单个1跟随零或更多0 s。此点之前的解决方案部分是完全位于[0, n-1]的步行,从0开始,以n-1结束,步数少于t。< / p>

因此,您可以将原始问题简化为稍微简单的问题,即确定t中以[0, n]开头并以{结尾的0步数的步数。 {1}}(其中每个步骤可以是n0+1,与以前一样。)

以下代码解决了更简单的问题。它使用-1装饰器来缓存中间结果;这是在Python 3的标准库中,或者你可以为Python 2下载recipe

lru_cache

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

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)