可从k
项中检索的N
项的组合数由以下公式描述。
N!
c = ___________________
(k! * (N - k)!)
一个例子是在彩票抽奖中可以从6 Balls
的鼓中抽取多少48 Balls
组合。
优化此公式以使用最小的O时间复杂度
运行这个问题的灵感来自新的WolframAlpha数学引擎,以及它可以非常快速地计算出非常大的组合。例如以及随后在另一个论坛上讨论该主题。
http://www97.wolframalpha.com/input/?i=20000000+Choose+15000000
在一些人尝试解决方案之后,我会发布一些来自该讨论的信息/链接。
任何语言都可以接受。
答案 0 :(得分:6)
Python: O (min [ k , n - k ] 2 )
def choose(n,k):
k = min(k,n-k)
p = q = 1
for i in xrange(k):
p *= n - i
q *= 1 + i
return p/q
分析:
p
和q
的大小会在循环内线性增加,如果可以认为n-i
和1+i
具有不变的大小。k
上的算术系列。我的结论: O (k
2 )
如果重写为使用浮点数,则乘法将是原子操作,但是我们将失去很多精度。它甚至溢出choose(20000000, 15000000)
。 (不是很大的惊喜,因为结果大约是0.2119620413×10 4884378 。)
def choose(n,k):
k = min(k,n-k)
result = 1.0
for i in xrange(k):
result *= 1.0 * (n - i) / (1 + i)
return result
答案 1 :(得分:5)
请注意,WolframAlpha返回“十进制逼近”。如果您不需要绝对精度,则可以通过使用Stirling's Approximation计算阶乘来做同样的事情。
现在,斯特林的近似需要评估(n / e)^ n,其中e是自然对数的基础,这是迄今为止最慢的运算。但这可以使用another stackoverflow post中列出的技术来完成。
如果使用双精度和重复平方来完成求幂,则操作将为:
操作次数可能会有点聪明,但使用这种方法总时间复杂度将为O(log n)。非常易于管理。
编辑:考虑到这种计算的常见程度,关于这一主题的学术文献必然会有很多。一个好的大学图书馆可以帮助你追踪它。EDIT2:另外,正如在另一个响应中指出的那样,这些值很容易溢出一个double,因此即使是适度大的k和n值,也需要使用具有非常高精度的浮点类型。
答案 2 :(得分:2)
答案 3 :(得分:2)
Python 近似 O (1)?
使用python十进制实现来计算近似值。由于它不使用任何外部循环,并且数字的大小有限,我认为它将在 O (1)中执行。
from decimal import Decimal
ln = lambda z: z.ln()
exp = lambda z: z.exp()
sinh = lambda z: (exp(z) - exp(-z))/2
sqrt = lambda z: z.sqrt()
pi = Decimal('3.1415926535897932384626433832795')
e = Decimal('2.7182818284590452353602874713527')
# Stirling's approximation of the gamma-funciton.
# Simplification by Robert H. Windschitl.
# Source: http://en.wikipedia.org/wiki/Stirling%27s_approximation
gamma = lambda z: sqrt(2*pi/z) * (z/e*sqrt(z*sinh(1/z)+1/(810*z**6)))**z
def choose(n, k):
n = Decimal(str(n))
k = Decimal(str(k))
return gamma(n+1)/gamma(k+1)/gamma(n-k+1)
示例:
>>> choose(20000000,15000000)
Decimal('2.087655025913799812289651991E+4884377')
>>> choose(130202807,65101404)
Decimal('1.867575060806365854276707374E+39194946')
任何更高,它会溢出。指数似乎限于40000000。
答案 4 :(得分:1)
给定n和K的合理数量的值,提前计算它们并使用查找表。
它以某种方式避开了这个问题(你正在卸载计算),但如果你必须确定大量的值,这是一个很有用的技术。
答案 5 :(得分:1)
<强> MATLAB:强>
骗子的方式(使用内置函数NCHOOSEK): 13个字符,O(?)
nchoosek(N,k)
我的解决方案: 36个字符,O(分(k,N-k))
a=min(k,N-k);
prod(N-a+1:N)/prod(1:a)
答案 6 :(得分:1)
我知道这是一个非常古老的问题,但是我在这个问题的解决方案上挣扎了很长一段时间,直到我找到一个用VB 6编写的非常简单的解决方案并将其移植到C#之后,结果如下:
public int NChooseK(int n, int k)
{
var result = 1;
for (var i = 1; i <= k; i++)
{
result *= n - (k - i);
result /= i;
}
return result;
}
最终的代码很简单,你不会相信它会运行直到你运行它。
此外,original article给出了他如何达到最终算法的一些很好的解释。