【问题标题】:Finding the LCM of a range of numbers查找一系列数字的 LCM
【发布时间】:2008-10-09 03:00:30
【问题描述】:

我今天读到了一篇有趣的 DailyWTF 帖子,"Out of All The Possible Answers...",它让我很感兴趣,以至于我挖出了原来的 forum post。这让我开始思考如何解决这个有趣的问题——最初的问题是在Project Euler 上提出的:

2520 是可以除以每个 从 1 到 10 的数字,没有任何余数。

能被所有元素整除的最小数是多少 从 1 到 20 的数字?

要将这个问题改造成一个编程问题,你将如何创建一个函数来找到任意数字列表的最小公倍数?

尽管我对编程很感兴趣,但我在纯数学方面非常糟糕,但经过一些谷歌搜索和一些实验后,我能够解决这个问题。我很好奇 SO 用户可能采取的其他方法。如果您愿意,请在下面发布一些代码,并附上解释。请注意,虽然我确信存在用于以各种语言计算 GCD 和 LCM 的库,但我更感兴趣的是比调用库函数更直接地显示逻辑的东西 :-)

我最熟悉 Python、C、C++ 和 Perl,但欢迎使用任何您喜欢的语言。为像我这样的其他数学挑战者解释逻辑的奖励积分。

编辑:提交后我确实发现了这个类似的问题Least common multiple for 3 or more numbers,但它的回答与我已经弄清楚的相同基本代码并且没有真正的解释,所以我觉得这已经足够不同了保持打开状态。

【问题讨论】:

  • 顺便说一句 - 答案是 10581480?我相信这是特定问题的正确答案:)
  • @warren - 我被你的回答吓坏了,因为有一段时间我认为你是对的(刚刚发现这个问题),而我在 Haskell 中编写的 lcm 计算器给出了一个截然不同的答案,并且 10581480 似乎可以被 [1..20] 整除。我一定在某处犯了错误,因为您的答案不能被 11 或 16 整除。正确答案是 232792560。
  • @Matt Ellen - 打得很好,不知道我的回答是怎么漏掉 11 的 :)

标签: algorithm math lcm


【解决方案1】:

答案根本不需要任何花哨的步法,就因式分解或主幂而言,而且最肯定不需要埃拉托色尼筛。

相反,您应该通过使用欧几里得算法(不需要分解,实际上速度明显更快)计算 GCD 来计算单对的 LCM:


def lcm(a,b):
    gcd, tmp = a,b
    while tmp != 0:
        gcd,tmp = tmp, gcd % tmp
    return a*b/gcd

然后你可以使用上面的 lcm() 函数找到我减少数组的总 LCM:


reduce(lcm, range(1,21))

【讨论】:

  • 确实,这与我最终得到的算法相同,以及作为类似问题的解决方案提供的算法。似乎是如何解决这个问题的普遍共识。
  • 使用 return a*(b/gcd) 或 return (a/gcd)*b。因为 a*b 可以是一个很大的数。
【解决方案2】:

这个问题很有趣,因为它不需要你找到任意一组数字的 LCM,你得到了一个连续的范围。您可以使用Sieve of Eratosthenes 的变体来找到答案。

def RangeLCM(first, last):
    factors = range(first, last+1)
    for i in range(0, len(factors)):
        if factors[i] != 1:
            n = first + i
            for j in range(2*n, last+1, n):
                factors[j-first] = factors[j-first] / factors[i]
    return reduce(lambda a,b: a*b, factors, 1)


编辑:最近的一次投票让我重新审视了这个超过 3 年的答案。我的第一个观察是,我今天会用enumerate 来写它。为了使其与 Python 3 兼容,需要进行一些小的更改。

第二个观察结果是,该算法仅在范围的起点为 2 或更小时才有效,因为它不会尝试筛选出范围起点以下的公因子。例如,RangeLCM(10, 12) 返回 1320 而不是正确的 660。

第三个观察结果是,没有人试图将此答案与任何其他答案进行计时。我的直觉说,随着范围变大,这将比蛮力 LCM 解决方案有所改善。测试证明我的直觉是正确的,至少这一次。

由于该算法不适用于任意范围,因此我将其重写为假设范围从 1 开始。我在最后删除了对 reduce 的调用,因为计算结果更容易,因为因子是生成。相信新版本的函数更正确也更容易理解。

def RangeLCM2(last):
    factors = list(range(last+1))
    result = 1
    for n in range(last+1):
        if factors[n] > 1:
            result *= factors[n]
            for j in range(2*n, last+1, n):
                factors[j] //= factors[n]
    return result

以下是与原始的一些时间比较以及Joe Bebel 提出的解决方案,在我的测试中称为RangeEuclid

>>> t=timeit.timeit
>>> t('RangeLCM.RangeLCM(1, 20)', 'import RangeLCM')
17.999292996735676
>>> t('RangeLCM.RangeEuclid(1, 20)', 'import RangeLCM')
11.199833288867922
>>> t('RangeLCM.RangeLCM2(20)', 'import RangeLCM')
14.256165588084514
>>> t('RangeLCM.RangeLCM(1, 100)', 'import RangeLCM')
93.34979585394194
>>> t('RangeLCM.RangeEuclid(1, 100)', 'import RangeLCM')
109.25695507389901
>>> t('RangeLCM.RangeLCM2(100)', 'import RangeLCM')
66.09684505991709

对于问题中给出的 1 到 20 的范围,欧几里得算法击败了我的旧答案和新答案。对于 1 到 100 的范围,您可以看到基于筛的算法领先,尤其是优化版本。

【讨论】:

  • 谢谢马克!这正是我想到的那种有趣的回复:-)
  • 这很聪明——我没有这么想过:)
【解决方案3】:

有一个快速的解决方案,只要范围是 1 到 N。

关键的观察是如果n (p_1^a_1 * p_2^a_2 * ... p_k * a_k, 那么它将对 LCM 贡献与p_1^a_1p_2^a_2,...p_k^a_k 完全相同的因素。而且这些权力中的每一个也在1到N的范围内。因此,我们只需要考虑小于 N 的最高纯素数。

例如我们有 20 个

2^4 = 16 < 20
3^2 = 9  < 20
5^1 = 5  < 20
7
11
13
17
19

将所有这些素数相乘,我们得到所需的结果

2*2*2*2*3*3*5*7*11*13*17*19 = 232792560

所以在伪代码中:

def lcm_upto(N):
  total = 1;
  foreach p in primes_less_than(N):
    x=1;
    while x*p <= N:
      x=x*p;
    total = total * x
  return total

现在您可以调整内部循环以稍微不同的工作方式来获得更快的速度,并且您可以预先计算 primes_less_than(N) 函数。

编辑:

由于最近的一次投票,我决定重新审视这个,看看与其他列出的算法的速度比较如何。

针对 Joe Beibers 和 Mark Ransoms 方法的 1-160 范围和 10k 次迭代的时间安排如下:

乔:1.85 秒 分数:3.26s 我的:0.33s

这是一个日志图,结果高达 300。

我的测试代码可以在这里找到:

import timeit


def RangeLCM2(last):
    factors = range(last+1)
    result = 1
    for n in range(last+1):
        if factors[n] > 1:
            result *= factors[n]
            for j in range(2*n, last+1, n):
                factors[j] /= factors[n]
    return result


def lcm(a,b):
    gcd, tmp = a,b
    while tmp != 0:
        gcd,tmp = tmp, gcd % tmp
    return a*b/gcd

def EuclidLCM(last):
    return reduce(lcm,range(1,last+1))

primes = [
 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 
 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 
 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 
 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 
 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 
 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 
 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 
 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 
 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 
 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 
 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 
 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 
 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 
 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 
 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 
 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 
 947, 953, 967, 971, 977, 983, 991, 997 ]

def FastRangeLCM(last):
    total = 1
    for p in primes:
        if p>last:
            break
        x = 1
        while x*p <= last:
            x = x * p
        total = total * x
    return total


print RangeLCM2(20)
print EculidLCM(20)
print FastRangeLCM(20)

print timeit.Timer( 'RangeLCM2(20)', "from __main__ import RangeLCM2").timeit(number=10000)
print timeit.Timer( 'EuclidLCM(20)', "from __main__ import EuclidLCM" ).timeit(number=10000)
print timeit.Timer( 'FastRangeLCM(20)', "from __main__ import FastRangeLCM" ).timeit(number=10000)

print timeit.Timer( 'RangeLCM2(40)', "from __main__ import RangeLCM2").timeit(number=10000)
print timeit.Timer( 'EuclidLCM(40)', "from __main__ import EuclidLCM" ).timeit(number=10000)
print timeit.Timer( 'FastRangeLCM(40)', "from __main__ import FastRangeLCM" ).timeit(number=10000)

print timeit.Timer( 'RangeLCM2(60)', "from __main__ import RangeLCM2").timeit(number=10000)
print timeit.Timer( 'EuclidLCM(60)', "from __main__ import EuclidLCM" ).timeit(number=10000)
print timeit.Timer( 'FastRangeLCM(60)', "from __main__ import FastRangeLCM" ).timeit(number=10000)

print timeit.Timer( 'RangeLCM2(80)', "from __main__ import RangeLCM2").timeit(number=10000)
print timeit.Timer( 'EuclidLCM(80)', "from __main__ import EuclidLCM" ).timeit(number=10000)
print timeit.Timer( 'FastRangeLCM(80)', "from __main__ import FastRangeLCM" ).timeit(number=10000)

print timeit.Timer( 'RangeLCM2(100)', "from __main__ import RangeLCM2").timeit(number=10000)
print timeit.Timer( 'EuclidLCM(100)', "from __main__ import EuclidLCM" ).timeit(number=10000)
print timeit.Timer( 'FastRangeLCM(100)', "from __main__ import FastRangeLCM" ).timeit(number=10000)

print timeit.Timer( 'RangeLCM2(120)', "from __main__ import RangeLCM2").timeit(number=10000)
print timeit.Timer( 'EuclidLCM(120)', "from __main__ import EuclidLCM" ).timeit(number=10000)
print timeit.Timer( 'FastRangeLCM(120)', "from __main__ import FastRangeLCM" ).timeit(number=10000)

print timeit.Timer( 'RangeLCM2(140)', "from __main__ import RangeLCM2").timeit(number=10000)
print timeit.Timer( 'EuclidLCM(140)', "from __main__ import EuclidLCM" ).timeit(number=10000)
print timeit.Timer( 'FastRangeLCM(140)', "from __main__ import FastRangeLCM" ).timeit(number=10000)

print timeit.Timer( 'RangeLCM2(160)', "from __main__ import RangeLCM2").timeit(number=10000)
print timeit.Timer( 'EuclidLCM(160)', "from __main__ import EuclidLCM" ).timeit(number=10000)
print timeit.Timer( 'FastRangeLCM(160)', "from __main__ import FastRangeLCM" ).timeit(number=10000)

【讨论】:

  • 你可以说primes = [2,3]+[x for x in range(5,1000,2) if pow(3,x-1,x)==1==pow(2,x-1,x)],而不是那个冗长的素数列表
  • @Michael 你用什么算法找到素数?
  • @tilaprimera 我想我只是从素数列表中复制了它们。但是,您可以在此站点上找到许多计算素数的快速算法。
  • 谢谢,我正在研究埃拉托色尼筛法
【解决方案4】:

Haskell 中的单行代码。

wideLCM = foldl lcm 1

这是我在自己的 Project Euler 问题 5 中使用的。

【讨论】:

    【解决方案5】:

    在 Haskell 中:

    listLCM xs =  foldr (lcm) 1 xs
    

    你可以传递一个列表,例如:

    *Main> listLCM [1..10]
    2520
    *Main> listLCM [1..2518]
    266595767785593803705412270464676976610857635334657316692669925537787454299898002207461915073508683963382517039456477669596355816643394386272505301040799324518447104528530927421506143709593427822789725553843015805207718967822166927846212504932185912903133106741373264004097225277236671818323343067283663297403663465952182060840140577104161874701374415384744438137266768019899449317336711720217025025587401208623105738783129308128750455016347481252967252000274360749033444720740958140380022607152873903454009665680092965785710950056851148623283267844109400949097830399398928766093150813869944897207026562740359330773453263501671059198376156051049807365826551680239328345262351788257964260307551699951892369982392731547941790155541082267235224332660060039217194224518623199770191736740074323689475195782613618695976005218868557150389117325747888623795360149879033894667051583457539872594336939497053549704686823966843769912686273810907202177232140876251886218209049469761186661055766628477277347438364188994340512556761831159033404181677107900519850780882430019800537370374545134183233280000
    

    【讨论】:

      【解决方案6】:

      一个或多个数字的 LCM 是所有数字中所有不同素因数的乘积,每个素数是该素数出现在数字中的所有幂的最大值的幂的LCM。

      假设 900 = 2^3 * 3^2 * 5^2,26460 = 2^2 * 3^3 * 5^1 * 7^2。 2 的最大幂是 3,3 的最大幂是 3,5 的最大幂是 1,7 的最大幂是 2,任何更高质数的最大幂都是 0。 所以 LCM 为:264600 = 2^3 * 3^3 * 5^2 * 7^2。

      【讨论】:

      • 感谢您的回复,看来您和 Warren 提供了相同的基本答案。我希望人们会发布示例,展示在代码中解决此问题的不同方法,如果您可以发布使用此技术的算法方法,那就太好了!
      【解决方案7】:
      print "LCM of 4 and 5 = ".LCM(4,5)."\n";
      
      sub LCM {
          my ($a,$b) = @_;    
          my ($af,$bf) = (1,1);   # The factors to apply to a & b
      
          # Loop and increase until A times its factor equals B times its factor
          while ($a*$af != $b*$bf) {
              if ($a*$af>$b*$bf) {$bf++} else {$af++};
          }
          return $a*$af;
      }
      

      【讨论】:

        【解决方案8】:

        Haskell 中的一种算法。这是我现在认为的算法思维语言。这可能看起来很奇怪、复杂且不受欢迎——欢迎使用 Haskell!

        primes :: (Integral a) => [a]
        --implementation of primes is to be left for another day.
        
        primeFactors :: (Integral a) => a -> [a]
        primeFactors n = go n primes where
            go n ps@(p : pt) =
                if q < 1 then [] else
                if r == 0 then p : go q ps else
                go n pt
                where (q, r) = quotRem n p
        
        multiFactors :: (Integral a) => a -> [(a, Int)]
        multiFactors n = [ (head xs, length xs) | xs <- group $ primeFactors $ n ]
        
        multiProduct :: (Integral a) => [(a, Int)] -> a
        multiProduct xs = product $ map (uncurry (^)) $ xs
        
        mergeFactorsPairwise [] bs = bs
        mergeFactorsPairwise as [] = as
        mergeFactorsPairwise a@((an, am) : _) b@((bn, bm) : _) =
            case compare an bn of
                LT -> (head a) : mergeFactorsPairwise (tail a) b
                GT -> (head b) : mergeFactorsPairwise a (tail b)
                EQ -> (an, max am bm) : mergeFactorsPairwise (tail a) (tail b)
        
        wideLCM :: (Integral a) => [a] -> a
        wideLCM nums = multiProduct $ foldl mergeFactorsPairwise [] $ map multiFactors $ nums
        

        【讨论】:

        • 我认为你过度设计了它
        • 我已经为其他目的(主要是数学兴趣)编写了 primeFactors、multiFactors 和 multiProduct。其他两个功能还不错。
        【解决方案9】:

        这是我的 Python 尝试:

        #!/usr/bin/env python
        
        from operator import mul
        
        def factor(n):
            factors = {}
            i = 2 
            while i <= n and n != 1:
                while n % i == 0:
                    try:
                        factors[i] += 1
                    except KeyError:
                        factors[i] = 1
                    n = n / i
                i += 1
            return factors
        
        base = {}
        for i in range(2, 2000):
            for f, n in factor(i).items():
                try:
                    base[f] = max(base[f], n)
                except KeyError:
                    base[f] = n
        
        print reduce(mul, [f**n for f, n in base.items()], 1)
        

        第一步得到一个数的质因数。第二步构建每个因子被看到的最大次数的哈希表,然后将它们全部相乘。

        【讨论】:

          【解决方案10】:

          这可能是迄今为止我见过的最简洁、最短的答案(无论是就代码行而言)。

          def gcd(a,b): return b and gcd(b, a % b) or a
          def lcm(a,b): return a * b / gcd(a,b)
          
          n = 1
          for i in xrange(1, 21):
              n = lcm(n, i)
          

          来源:http://www.s-anand.net/euler.html

          【讨论】:

            【解决方案11】:

            这是我在 JavaScript 中的答案。我首先从素数着手,并开发了一个很好的可重用代码函数来查找素数并查找素数因子,但最终决定这种方法更简单。

            我的答案中没有什么独特之处没有在上面发布,它只是在 Javascript 中,我没有具体看到。

            //least common multipe of a range of numbers
            function smallestCommons(arr) {
               arr = arr.sort();
               var scm = 1; 
               for (var i = arr[0]; i<=arr[1]; i+=1) { 
                    scm =  scd(scm, i); 
                }
              return scm;
            }
            
            
            //smallest common denominator of two numbers (scd)
            function scd (a,b) {
                 return a*b/gcd(a,b);
            }
            
            
            //greatest common denominator of two numbers (gcd)
            function gcd(a, b) {
                if (b === 0) {  
                    return a;
                } else {
                   return gcd(b, a%b);
                }
            }       
            
            smallestCommons([1,20]);
            

            【讨论】:

              【解决方案12】:

              这是我的 javascript 解决方案,我希望你觉得它很容易理解:

              function smallestCommons(arr) {
                var min = Math.min(arr[0], arr[1]);
                var max = Math.max(arr[0], arr[1]);
              
                var smallestCommon = min * max;
              
                var doneCalc = 0;
              
                while (doneCalc === 0) {
                  for (var i = min; i <= max; i++) {
                    if (smallestCommon % i !== 0) {
                      smallestCommon += max;
                      doneCalc = 0;
                      break;
                    }
                    else {
                      doneCalc = 1;
                    }
                  }
                }
              
                return smallestCommon;
              }
              

              【讨论】:

                【解决方案13】:

                这是使用 C 语言的解决方案

                #include<stdio.h>
                    int main(){
                    int a,b,lcm=1,small,gcd=1,done=0,i,j,large=1,div=0;
                    printf("Enter range\n");
                    printf("From:");
                    scanf("%d",&a);
                    printf("To:");
                    scanf("%d",&b);
                    int n=b-a+1;
                    int num[30];
                    for(i=0;i<n;i++){
                        num[i]=a+i;
                    }
                    //Finds LCM
                    while(!done){
                        for(i=0;i<n;i++){
                            if(num[i]==1){
                                done=1;continue;
                            }
                            done=0;
                            break;
                        }
                        if(done){
                            continue;
                        }
                        done=0;
                        large=1;
                        for(i=0;i<n;i++){
                            if(num[i]>large){
                                large=num[i];
                            }
                        }
                        div=0;
                        for(i=2;i<=large;i++){
                            for(j=0;j<n;j++){
                                if(num[j]%i==0){
                                    num[j]/=i;div=1;
                                }
                                continue;
                            }
                            if(div){
                                lcm*=i;div=0;break;
                            }
                        }
                    }
                    done=0;
                    //Finds GCD
                    while(!done){
                        small=num[0];
                        for(i=0;i<n;i++){
                            if(num[i]<small){
                                small=num[i];
                            }
                        }
                        div=0;
                        for(i=2;i<=small;i++){
                            for(j=0;j<n;j++){
                                if(num[j]%i==0){
                                    div=1;continue;
                                }
                                div=0;break;
                            }
                            if(div){
                                for(j=0;j<n;j++){
                                    num[j]/=i;
                                }
                                gcd*=i;div=0;break;
                            }
                        }
                        if(i==small+1){
                            done=1;
                        }
                    }
                    printf("LCM = %d\n",lcm);
                    printf("GCD = %d\n",gcd);
                    return 0;
                }
                

                【讨论】:

                  【解决方案14】:

                  在扩展@Alexander 的评论时,我要指出,如果您可以将数字分解为素数,删除重复项,然后相乘,您就会得到答案。

                  例如,1-5 的质因数是 2,3,2,2,5。从“4”的因子列表中删除重复的“2”,得到 2,2,3,5。将它们相乘得到 60,这就是您的答案。

                  上一条评论中提供的 Wolfram 链接 http://mathworld.wolfram.com/LeastCommonMultiple.html 采用更正式的方法,但上面是简短版本。

                  干杯。

                  【讨论】:

                  • 不清楚“删除重复项”是什么意思。您正在从 2 中删除“2”,因为它是由 4 的因式分解捕获的。这不仅仅是语义。正义说得好一点。 . .您只想取每个素数的最大幂的值。然后,您知道较低的权力是因素。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-08-12
                  • 1970-01-01
                  • 2021-11-17
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多