这道题是计算最小公倍数 (LCM) 的问题。解决它的几种标准方法是使用最大公约数 (GCD)、素数分解和约简。
GCD 开始发挥作用,因为对于两个整数 a 和 b,
GCD(a,b)*LCM(a,b) = a*b
众所周知,使用欧几里得算法计算 GCD,虽然可能不是那个名字,但可以表示为
def gcd(x,y):
while y:
x,y = y,x%y
return abs(x)
两个数字的 GCD 和 LCM 本身并不能解决两个以上的问题,但是它们都具有关联性,这是减少两个以上数字的设置。这意味着:
GCD(a,b,c,d)=GCD(GCD(GCD(a,b),c),d) # associativity
LCM(a,b,c,d)=LCM(LCM(LCM(a,b),c),d) # associativity
还有一些有趣的功能
reduce(fun,[a,b,c,d])
等价于
fun(fun(fun(a,b),c),d) # expanded reductive form
这意味着
GCD(a,b,c,d) = reduce(GCD,[a,b,c,d])
LCM 关联性还意味着只需要一个两个参数的 LCM 函数来减少超过两个,并且不需要 GCD。这很好,因为它减少了开销,因为直接计算 LCM 所需的操作比使用 GCD 少。
如果您想知道,GCD 和 LCM 也是可交换的。这仅仅意味着参数列表中元素的顺序不会影响结果。
根据这些信息、对 150 多个 LCM 实现的回顾和一些测试,以下 Python LCM 函数在性能和简单性方面表现最佳:
def lcm(*args):
def lcm2(x,y):
tmp=x
while (tmp%y)!=0:
tmp+=x
return tmp
return reduce(lcm2,args)
此函数来自 Eratosthenes 在Least common multiple for 3 or more numbers 上的回答。
让我们看看它在使用 Python 3.4.3 x64 时的表现
from functools import reduce
a = [2, 3, 4, 5, 6, 7, 8, 9, 10]
lcm(*a)
Out[9]: 2520
timeit lcm(*a)
100000 loops, best of 3: 5.95 µs per loop
timeit lcm(2, 3, 4, 5, 6, 7, 8, 9, 10)
100000 loops, best of 3: 5.9 µs per loop
对于 Python 2.7.10 x64
lcm(*a)
Out[60]: 2520
timeit lcm(*a)
100000 loops, best of 3: 4.36 µs per loop
timeit lcm(2, 3, 4, 5, 6, 7, 8, 9, 10)
100000 loops, best of 3: 4.33 µs per loop
LCM 也可以使用素数分解来计算。算术基本定理,也称为唯一因式分解定理,它说“每个大于 1 的整数要么是素数本身,要么是素数的乘积,并且这个乘积是唯一的,直到因式的顺序。”这意味着除数列表中的每个除数都有一个素数分解,并且所有这些分解中的每个素数至少出现一个除数的最大次数。因此,可以被所有除数(也就是它们的 LCM)均分的最小自然数是所有出现的素数的乘积,每个素数都提高到其最大次数的幂。下面是这个方法的实现代码:
from collections import defaultdict
from operator import mul
try:
reduce
except:
from functools import reduce
def findmnn(a):
def prime_factors(n):
factors = defaultdict(int)
d = 2
step = 1
while d*d <= n:
while n>1:
while n%d == 0:
factors[d] += 1
n = n/d
d += step
step = 2
return factors
d = defaultdict(int)
for i in a:
p = prime_factors(i)
for j in p:
if p[j] > d[j]:
d[j] = p[j]
return reduce(mul, map(lambda x: x**d[x], d))
给定除数列表为
a = [2, 3, 4, 5, 6, 7, 8, 9, 10]
findmnn(a)
Out[3]: 2520
使用 Python 2.7.10 x64 使用 timeit 进行测试
timeit findmnn(a)
10000 loops, best of 3: 23.1 µs per loop
在同一平台上使用 Python 3.4.3 x64 使用 timeit 进行测试:
timeit findmnn(a)
10000 loops, best of 3: 49.4 µs per loop
为了比较和在同一平台上:
def mpmnn():
n = 0
while True:
n += 10
if all(n % i == 0 for i in (6, 7, 8, 9)):
return n
使用 Python 2.7.10 x64
timeit mpmnn()
1000 loops, best of 3: 245 µs per loop
使用 Python 3.4.3 x64
timeit mpmnn()
1000 loops, best of 3: 244 µs per loop
这里有一些参考资料
- Least Common Multiple (Wolfram World)
- Least Common Multiple Code Examples (Rosetta Code)
- Greatest Common Divisor (Wolfram World)
- Euclidean Algorithm (Wolfram World)
- Fundamental theorem of arithmetic (WikepediA)