找出数字范围的最小公倍数 (Smallest Common Multiple)
题目链接
问题解释
- 这个
function
接收一个数组参数arr
,其中包含两个数字元素。返回这两个数字之间所有数的最小公倍数 - 如果
arr
是[1, 5]
,那么返回值应为60
- 需要注意的是,这个
arr
是没有排序的,因此对于[5, 1]
,也应该返回60
解题思路
- 先回顾一些定义。如果存在一个数
x
,可以被a
和b
分别整除,且x
大于等于a
和b
,则称x
为a
和b
的公倍数。a
和b
的公倍数有无限多个,其中最小的就叫最小公倍数 - 经常与最小公倍数一起提及的还有最大公约数,也叫最大公因数。指的是某几个整数共有约数中最大的一个。约数的定义在上一篇文章中提到过,也叫因数
- 这道题目的关键在于最小公倍数的算法。在基本的解法中我们先用循环来解决
- 先要明白一点,最小公倍数不会超过两个数的乘积。这个结论很重要,因为我们可以通过这个来确定循环的边界
- 后面我们还会谈到计算最小公倍数的其他算法。基本解法的思路并不复杂,可以根据上面的提示先试着自己写一写
基本解法 - 遍历
思路提示
- 由于题目中要判断范围内多个数字的最小公倍数,最简单且暴力的方法就是两两地进行判断
- 既然需要多次判断,那么我们首先需要封装一个判断函数,用于得出两个数字的最小公倍数。当然,写成递归也是没问题的。或者,用数组的
reduce
方法也行 - 有一点需要注意。记”求最小公倍数”为
LCM
。对于求三个数字a
、b
和c
的最小公倍数LCM(a, b, c)
,则相当于LCM(LCM(a, b), c)
。由于LCM
也存在结合律,因此也相当于LCM(a, LCM(b, c))
。所以,先求哪一组都是可以的
代码 - for 循环
1 | function smallestCommons(arr) { |
解释
- 首先,根据传入的数组参数,取出较大值和较小值,方便后续操作
- 生成数组那里应该不用多说。至于初始值的设置,原因在于,
n
与n + 1
的最小公倍数一定是n * (n + 1)
。这样,我们只要把这个结果带到第三个数 (索引为2
) 继续计算就好了 - 至于求两个数最小公倍数的方法,比如
n
和m
,我们只需要去测试n
、2n
、3n
、4n
……是否能被m
整除。当然,试到m * n
就够了,因为这个数肯定能被m
整除 - 当然,
n
和m
都不能为0
。这个要进行边界判断 - 如果还有细节想不明白的,再看一下代码中的注释吧
代码 - reduce
- 之前说过,这里也可以用
reduce
去实现。因为本身就是一个迭代的过程。getSCM
和上面的一样,这里就不重复了
1 | function smallestCommons(arr) { |
优化 - 数学方法
思路提示
- 有一种计算最小公倍数的数学方法,但要先计算出最大公约数。详情可以参考最小公倍数的维基百科词条
- 计算最大公约数,有一种比较快捷的方法叫辗转相除法
- 简单举个例子,对于数字
a
和b
,若要求他们的最大公约数,则将较大数不断减去较小数,直到所得的差小于较小数。重复此步骤,直到差为 0,此时较小数即为最大公约数,伪代码如下:
1 | while(b !== 0) |
- 有朋友可能觉得这里需要处理特殊值。但由于任何数都可以整除
0
,因此0
与任何数的最大公约数都为那个数本身。所以,不需要进行特殊处理 - 根据维基百科,任何两个整数的最大公约数与最小公倍数存在如下关系:
1 | LCM(a, b) = |a * b| / GCD(a, b) |
- 其中,
LCM
为最小公倍数,GCD
为最大公约数。同样,这个可以推论到n
个数的情况,即:
1 | LCM(A1, A2, A3, ...) = ∏(An) / GCD(A1, A2, A3, ...) |
代码
1 | function smallestCommons(arr) { |
解释
- 要解释的不多了,如果看不明白,请再回去看一下上面的解释以及维基百科链接
- 这里用到了
.apply
,原因在于Math.min
和Math.max
接受的参数是多个数字。如果我们想给它传入数组参数,就需要用apply
,因为apply
的第二个参数就是数组。熟悉 ES6 的朋友也可以写成Math.min(...arr)
,其中...
是 Spread Operator