LC-878.第N个神奇数字

题目描述

leetcode 困难题

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

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

示例1:

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

示例2:

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

提示1:

1
2
1 <= n <= 10^9
2 <= a, b <= 4 * 10^4

容斥原理 + 二分查找

这道题容易想到的做法是多指针,两个指针分别指向 $a$ 和 $b$ 的某个倍数,并通过不断比较找到第 $n$ 个数,该做法的时间复杂度为 $O(N)$,在该题的数据规模下显然会 TLE。

容易想到的优化方法是通过二分查找找到第 $n$ 个数,但是需要解决的问题是如何确定某个数 $i$ 前面的神奇数字的数量,将该数量设为 $x$。

可以发现每个神奇数字必然满足以下三种情况之一:

  • 为 $a$ 的倍数
  • 为 $b$ 的倍数
  • 为 $lcm(a, b)$ 的倍数

显然通过容斥原理可以得出:
$x$ = $i$ 前面的数为 $a$ 的倍数的数量 + $i$ 前面的数为 $b$ 的倍数的数量 - $i$ 前面的数为 $lcm(a, b)$ 的倍数的数量

也就是

$x = \lfloor \frac{i}{a} \rfloor + \lfloor \frac{i}{b} \rfloor - \lfloor \frac{i}{lcm} \rfloor$

答案为二分查找的左边界,也就是第一个满足的数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
private static final int MOD = (int)(1e9 + 7);
private int a;
private int b;
private long lcm;

public int nthMagicalNumber(int n, int a, int b) {
long l = Math.min(a, b);
long r = Long.MAX_VALUE;
this.a = a;
this.b = b;
this.lcm = lcm(a, b);
while(l < r){
long mid = l + (r - l >> 1);
if(get(mid) < n){
l = mid + 1;
}else{
r = mid;
}
}
return (int) (l % MOD);
}

private long get(long mid){
return (mid / a) + (mid / b) - (mid / lcm);
}

private long lcm(int a, int b){
return (a * b) / gcd(a, b);
}

private int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}

}

类似的,也可以转化成

$x = \lceil \frac{i}{a} \rceil + \lceil \frac{i}{b} \rceil - \lceil \frac{i}{lcm} \rceil$

此时答案为右边界,也就是最后一个满足的数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
private static final int MOD = (int)(1e9 + 7);
private int a;
private int b;
private long lcm;

public int nthMagicalNumber(int n, int a, int b) {
long l = Math.min(a, b);
long r = Long.MAX_VALUE;
this.a = a;
this.b = b;
this.lcm = lcm(a, b);
while(l < r){
long mid = l + (r - l >> 1);
if(get(mid) > n){
r = mid;
}else{
l = mid + 1;
}
}
return (int) ((l - 1) % MOD);
}

private long get(long mid){
double temp = mid;
return (long)(Math.ceil(temp / a) + Math.ceil(temp / b) - Math.ceil(temp / lcm));
}

private long lcm(int a, int b){
return (a * b) / gcd(a, b);
}

private int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}

}

LC-878.第N个神奇数字
https://wecgwm.github.io/2022/11/22/LC-878-第N个神奇数字/
作者
yichen
发布于
2022年11月22日
许可协议