第 N 个神奇数字

164 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

878. 第 N 个神奇数字 - 力扣(LeetCode)

一个正整数如果能被 ab 整除,那么它是神奇的。

给定三个整数 na , b ,返回第 n 个神奇的数字。因为答案可能很大,所以返回答案  109 + 7 取模 后的值。

示例 1:

输入: n = 1, a = 2, b = 3
输出: 2

示例 2:

输入: n = 4, a = 2, b = 3
输出: 6

提示:

  • 1 <= n <= 10^9
  • 2 <= 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)
其中mab的最小公倍数。

第n个神奇数字的最小值为Math.min(a, b),最大值为Math.min(a, b) * n,我们只需要在最小值和最大值之间用二分法查找到第一个数字满足n === f(num)即可,如解法一。

本题也可以用数学方法求解。还设mab的最小公倍数,则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;
};