从昨晚开始我就一直在想这个谜题。我已经想出了一个解决办法,但效率不高,所以我想看看是否有更好的办法。
难题是:
给定正整数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 = 2
,T = 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 as
expect
和a asseq
的实例时,遵循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)
。所以我很快意识到解决办法其实是:
前缀a是一些有效的
{0, 1}
组合,其正长度为l
,其中包含o
1
s在
l + 1
位置,我开始添加一个或多个-1
s并使用b来跟踪数字。最大值将是B + o
或取决于保留在A
中的槽数,以最小的为数。递归调用
SOLVE'(N, T, B)
在前面的
N = 2, T = 4
示例中,在其中一个搜索案例中,我将执行以下操作:让A的前缀为[1],我们就得到了
A = [1, -, -, -]
。开始添加
-1
。这里我只添加一个:A = [1, -1, -, -]
。递归调用
SOLVE'
,这里我将调用SOLVE'(2, 2, 0)
来解决最后两个问题。这里它只返回[1, 1]
。然后其中一个组合产生[1, -1, 1, 1]
。但是这个算法太慢了。
我想知道我该如何优化它,或者以任何不同的方式来看待这个可以提高性能的问题?(我只需要这个主意,而不是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)])
最佳答案
一个观察:假设n
至少是1
,你所陈述问题的每一个解决方案都以[1, 0, ..., 0]
的形式结束:即一个1
后跟零个或多个0
s。在这之前的部分解决方案是完全位于[0, n-1]
中、从0
开始、在n-1
结束并采取少于t
步骤的步行。
因此,您可以将原来的问题简化为一个稍微简单的问题,即确定t
中从[0, n]
开始到0
结束的n
步数(其中每个步可以是0
、+1
或-1
,与之前一样)。
下面的代码解决了更简单的问题它使用lru_cache
decorator来缓存中间结果;这在python 3的标准库中,或者有一个recipe可以为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
关于algorithm - 用于计算数字和的组合数得出固定结果的算法难题,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34709311/