开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
一个正整数如果能被 a 或 b 整除,那么它是神奇的。
给定三个整数 n , a , b ,返回第 n 个神奇的数字。因为答案可能很大,所以返回答案 对 109 + 7 取模 后的值。
示例 1:
输入: n = 1, a = 2, b = 3
输出: 2
示例 2:
输入: n = 4, a = 2, b = 3
输出: 6
提示:
1 <= n <= 10^92 <= a, b <= 4 * 10^4
思路
本题可以用二分查找解题。我们知道对于数字num,小于等于num且能被a整除的正整数个数为 Math.floor(num / a)。用f(num)表示小于等于num的神奇数字的个数,则
f(num) = Math.floor(num / a) + Math.floor(num / b) - Math.floor(num / m),
其中m为a和b的最小公倍数。
第n个神奇数字的最小值为Math.min(a, b),最大值为Math.min(a, b) * n,我们只需要在最小值和最大值之间用二分法查找到第一个数字满足n === f(num)即可,如解法一。
本题也可以用数学方法求解。还设m为a和b的最小公倍数,则f(m) = m/a + m/b - 1,即每m个正整数中有f(m)个神奇数字。设k为第n个神奇数字,j为第n%f(m)个神奇数字,则 f(j)===n%f(m), k = m * Math.floor(n/f(m)) + j,本题就转化为求第j(j<f(m))个神奇数字,可以直接用数学方法计算的j个神奇数字,如解法二。
解题
解法一
/**
* @param {number} n
* @param {number} a
* @param {number} b
* @return {number}
*/
var nthMagicalNumber = function (n, a, b) {
const MOD = 10 ** 9 + 7;
if (a > b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
// 求最大公约数
const gcd = (a, b) => {
const r = b % a;
return r === 0 ? a : gcd(r, a);
};
// 求最小公倍数
const lcm = (a, b) => (a * b) / gcd(a, b);
const m = lcm(a, b);
let left = a;
let right = n * a;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const count =
Math.floor(mid / a) + Math.floor(mid / b) - Math.floor(mid / m);
if (count >= n) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left % MOD;
};
解法二
/**
* @param {number} n
* @param {number} a
* @param {number} b
* @return {number}
*/
var nthMagicalNumber = function (n, a, b) {
const MOD = 10 ** 9 + 7;
if (a > b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
// 求最大公约数
const gcd = (a, b) => {
const r = b % a;
return r === 0 ? a : gcd(r, a);
};
// 求最小公倍数
const lcm = (a, b) => (a * b) / gcd(a, b);
const m = lcm(a, b);
const total = m / a + m / b - 1;
const count = n % total;
const idx = Math.floor(count / (1 + a / b));
const k = Math.min((idx + 1) * a, (count - idx) * b);
return (Math.floor(n / total) * m + k) % MOD;
};