LC-878. 第 N 个神奇数字

878. 第 N 个神奇数字

难度困难

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

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

示例 1:

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

示例 2:

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

提示:

  • 1 <= n <= 109
  • 2 <= a, b <= 4 * 104

二分答案 + 容斥原理

同1201. 丑数Ⅲ

令 f(x) 等于所有小于等于 x 的神奇数字的数量,f(x) 是单调递增的。所以我们可以使用二分查找找到第一个使 f(x) = n 的 x 的值。x 即为答案。

**如何求 f(x) ?**在区间 [0, x] 中有 x/a 个数能被 a 整除,有 x/b 个数能被 b 整除。但是 f(x) 不等于 x/a + x/b。因为区间 [0, x] 中有一些数既能被 a 整除也能被 b 整除,它们被加了两次,所以要减去这些数。

令 lcm 为 a 和 b 的最小公倍数。区间 [0, x] 既能被 a 整除也能被 b 整除的数就是 lcm 的倍数,它们有 x/lcm 个。

所以 f(x) = x/a + x/b - x/lcm

其中,lcm 可以根据 a 和 b 的最大公约数(gcd)求出:lcm(a, b) = a * b / gcd(a, b)。gcd 可以用辗转相除法求出。

class Solution {
    static final int MOD = (int)1e9+7;
    public int nthMagicalNumber(int n, int a, int b) {
        long lcm_ab = lcm(a, b);
        long l = Math.min(a, b), r = (long)Math.min(a, b) * n;
        while (l < r) {
            long m = l + (r - l) / 2;
            long count = m / a + m / b - m / lcm_ab;
            if (count < n) {
                l = m+1;
            } else {
                r = m;
            }
        }
        return (int) (r % MOD); // 这里注意(r % (MOD))的括号
    }
    // 求最小公倍数
    int lcm(int a, int b) {return a / gcd(a, b) * b;}
    //求最大公约数
    int gcd(int a, int b) {return b == 0 ? a : gcd(b, a % b);}
}

GCD辗转相除法(欧几里得算法)

int gcd(int a, int b){//求最大公约数
	return b == 0 ? a : gcd(b, a % b);
}

定理:两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数。最大公约数(Greatest Common Divisor)缩写为GCD。gcd(a,b) = gcd(b,a mod b) (不妨设a>b 且r=a mod b ,r不为0)

文字不好理解,举个实例:
134 / 18 = 7 … 8
18 / 8 = 2 … 2
8 / 2 = 4 … 0

​ 我们要找最大公约数,而134/18 的和 8/2 的最大公约数相等,所以我们只需要求出 8/2 的最大公约数,是不是就是开头说的换了两个数再求,而我们要知道,因为两数相除,余数为0,其除数必定为最大公约数,所以这里的2也就是我们要找的134/18 的最大公约数。

你可能感兴趣的:(算法刷题记录,算法)