问题描述
考虑数字按非降序排列的所有非负整数的序列,即从左到右排序的数字(OEIS 中的 A009994)。此序列以 0 开头,包括 29、33 和 112 等数字,但不包括 30、31、32 或 105。
现在,给定一个数字不减的数字 N,我需要找到它在序列中的位置。或者,我需要找出有多少个非减数字小于 N。
如果我们简单地生成序列中的每个数字直到找到 N,这可以在 O(N) 时间内完成。此外,我们可以预先计算序列直到某个最大数字,然后使用二分搜索来找到索引在 O(log N) 中。但是因为在我的程序中,对于不同的 N 值,这个计算需要重复数千次(在一个内部循环内),如果我有一个更有效的算法甚至一个封闭的公式来计算索引,那就太好了。
以防万一,在我的程序中,N 是一个以 7 为基数的四位数字,即 0 7。
我程序中 N 的 maximum index 是 C(7+4-1,4) - 1 = 10! / 4! / 6! - 1 = 209。
编辑:由于一些贡献者强调涉及重建序列的解决方案实际上有多好,我想指出内存使用也很重要。我承认上述解决方案并不可怕,但它们确实看起来很浪费。寻找排列索引的有点类似的问题有 a simple solution,不需要重构序列。
解决方法
我找到了一个封闭式解决方案,扩展了 A009994 是按字典顺序重复的组合序列的概念。
解决方案总结为以下Python代码:
>>> from math import factorial as fact
>>> C = lambda n,k: n>=k and fact(n) // fact(k) // fact(n-k) or 0
>>> I = lambda a,b,c,d: C(10,4) - C(9-a,4) - C(8-b,3) - C(7-c,2) - C(6-d,1) - 1
>>> I(0,0)
0
>>> I(2,2,3,5)
147
>>> I(6,6,6)
209
说明
让我们考虑以 10 为基数从左到右排序的 3 位 (S
=3) 数字的序列 K
(B
=10)
N
中的任何数字 S
都可以通过其中出现的每个数字的计数来唯一标识,例如如果我们有 1 次出现数字“2”和 2 次出现数字“3”,那么 N
必须是“233”。
知道所有可用数字后,我们可以使用 stars & bars notation 来表示 N
。在基数 10 中,我们有 10 个可用数字,因此我们需要 B
-1=9 条来分隔槽:
|||||||||
对于 N
= 233,将星星放入它们的位置:
||*|**||||||
现在我们有一个 B
-1+K
=12 个对象(9 条 + 3 星)的列表。列表中星星的位置(从0开始)为:(2,4,5).
简而言之,要将任何 N
转换为其星形和条形 3 元组表示,我们只需将 N
中的每个数字添加到其位置 在 N
本身:
233 -> (2+0,3+1,3+2) -> (2,5) (formula I)
同样,这个 3 元组唯一标识以 10 为基数的任何 3 位数字,其中的数字已排序。相反,为了生成任何数字排序的 3 位数字,我们可以从 12 个位置 {0,1,...,10,11} 的集合中选择三个不同的项目。 S
和所有可能的 3 元组的集合 T
之间存在一一对应的关系,这些三元组具有从 0 到 11 的不同数字。
这种表示的优点是不会发生重复,因为两颗星不能在同一位置。这意味着 T
只是 12 个项目的 3-combinations 的集合。
也很容易看出,按字典顺序排序 T
等同于按数字排序 S
。
从这点来说,我们可以使用教科书的公式进行组合。如问题中所述,这就是我用来查找 S
长度的方法。我不知道的是 formula to find the position of a combination in the list of all possible combinations sorted by ascending order 是:
I = C(n,k) - C(n-p1,k) - C(n-p2,k-1) - ... - C(n-pk,1) (formula II)
哪里:
- C 是 binomial coefficient:C(n,k) = n! / (k!* (n-k)!)
- n 是可用项目的总数(在我们的例子中 n=B-1+K=12)
- k 是每个组合中的项目数 (k=K=3)
- p1,p2,pk 是选择的项目(即我们的三元组的项目)
最后,将公式 I 塞入公式 II,我们有:
I = C(n,k) - C(n-d1,k) - C(n-d2+1,k-1) - ... - C(n-dk+k-1,1)
其中 d1,d2,dk 是 N 的数字
注意:当 N
的最高位数为 B
-1=9 时,此公式将包括 C(n,k) 其中 n
这是问题的一般解决方案。
,但是因为在我的程序中这个计算需要重复 对于不同的 N 值,数千次(在内部循环内), 如果我有一个更有效的算法就好了
让我们对不构建和索引序列的时间成本进行全面检查。让我们实现一个头脑简单的基于字典的解决方案:
from numpy import base_repr
table = {}
for number in range(int('6666',7) + 1):
representation = base_repr(number,base=7)
a,d,*rest = representation + '7' * 3
if b >= a and c >= b and d >= c:
table[int(representation)] = len(table)
计时一百万次查找,在我的系统上,这比您的“通用解决方案”快 15 倍。但是 2/5 的更快时间用于生成表。让我们更进一步,静态定义表:
table = {
0: 0,1: 1,2: 2,3: 3,4: 4,5: 5,# ...
4666: 204,5555: 205,5556: 206,5566: 207,5666: 208,6666: 209,}
在我的系统上进行一百万次查找,这比您的“通用解决方案”快 25 倍。
我们仍然有办法找到一个闭式公式解决方案,它也是一种高效算法。