什么是范围 LCM?
在介绍如何查询范围 LCM 之前,先来了解一下什么是范围 LCM。LCM(最小公倍数)是指一个数的倍数中同时也是另一个数的倍数的最小正整数。范围 LCM 就是求多个数字的 LCM,在一定范围内的最小值。
举个例子:在数字 1~5 中找到它们的 LCM。1 的倍数为 {1,2,3,4,5},2 的倍数为 {2,4},3 的倍数为 {3},4 的倍数为 {4},5 的倍数为 {5}。它们的公共倍数为 {2,4},即 LCM(1,2,3,4,5) = 2*2 = 4。
范围 LCM 查询的实现思路
暴力解法
最简单的思路是暴力枚举每一个数字,然后找出在给定范围内的最小 LCM。但是,如果有数的值特别大,这种方法将会非常耗时。因此,这种算法只适用于数字比较小的情况。
function rangeLCM(start, end, arr) {
let lcm = Number.MAX_SAFE_INTEGER;
let found = false;
for (let i = start; i <= end; i++) {
let tempLCM = 1;
for (let j = 0; j < arr.length; j++) {
if (i % arr[j] !== 0) {
break;
}
tempLCM = LCM(tempLCM, arr[j]);
if (j === arr.length - 1) {
found = true;
lcm = Math.min(lcm, tempLCM);
}
}
}
return found ? lcm : -1;
}
优化算法
上述算法的时间复杂度是 O(nmlog(m)),其中 n 是查询范围,m 是数组 arr 的长度。接下来我们介绍两种优化算法,分别是线性筛和倍增。
线性筛
我们可以利用线性筛的方法,利用一个 isPrime 数组记录每个数是否为质数。
function rangeLCM(start, end, arr) {
let lcm = Number.MAX_SAFE_INTEGER;
let found = false;
let isPrime = Array(end + 1).fill(true);
let primes = [];
for (let i = 2; i <= end; i++) {
if (isPrime[i]) {
primes.push(i);
}
for (let j = 0; j < primes.length && i * primes[j] <= end; j++) {
isPrime[i * primes[j]] = false;
if (i % primes[j] === 0) {
break;
}
}
}
for (let i = start; i <= end; i++) {
let tempLCM = 1;
let flag = true;
for (let j = 0; j < primes.length && primes[j] <= i; j++) {
if (i % primes[j] === 0 && arr.indexOf(primes[j]) !== -1) {
if (flag) {
tempLCM = primes[j];
flag = false;
} else {
tempLCM = LCM(tempLCM, primes[j]);
}
}
}
if (!flag && arr.every(num => tempLCM % num === 0)) {
found = true;
lcm = Math.min(lcm, tempLCM);
}
}
return found ? lcm : -1;
}
倍增
我们可以利用倍增的方法,将查询范围拆成若干个区间。在每个区间内单独求 LCM,然后将所有区间的 LCM 两两相乘。
function rangeLCM(start, end, arr) {
let lcm = Number.MAX_SAFE_INTEGER;
let found = false;
const N = end - start + 1;
let f = Array(N + 1).fill(0);
for (let j = 0; j < arr.length; j++) {
for (let i = Math.ceil(start / arr[j]); i * arr[j] <= end; i++) {
let idx = i * arr[j] - start;
let k = idx + 1;
while (k % arr[j] === 0) {
k /= arr[j];
}
if (f[idx]) {
f[idx] = LCM(f[idx], k);
} else {
f[idx] = k;
}
}
}
for (let i = 0; i < N; i++) {
if (f[i] === 0) {
continue;
}
if (found) {
lcm = LCM(lcm, f[i]);
} else {
lcm = f[i];
found = true;
}
}
return found ? lcm : -1;
}
总结
范围 LCM 查询的优化算法主要有线性筛和倍增两种方法。线性筛的时间复杂度为 O(mlog(log(m)) + nlog(m)),其中 n 是查询范围,m 是数组 arr 的长度。倍增的时间复杂度为 O(m + nlog(n/m)),其中 n 是查询范围,m 是计算区间的长度。当 m 很大时,倍增的时间复杂度比线性筛更优。