第 N 个神奇数字
难度:
标签:
题目描述
代码结果
运行时间: 25 ms, 内存: 16.0 MB
/*
* 思路:
* 虽然Java Streams不适合处理这样的问题,但我们可以借助一些额外的工具类来使用流的方式进行计算。
* 我们可以模拟流操作,但二分查找仍然是核心。
*/
import java.util.stream.LongStream;
public class MagicalNumberStream {
private static final int MOD = 1000000007;
public int nthMagicalNumber(int n, int a, int b) {
long low = Math.min(a, b);
long high = (long) n * Math.min(a, b);
long lcm = lcm(a, b);
while (low < high) {
long mid = low + (high - low) / 2;
if (LongStream.of(mid / a, mid / b).sum() - mid / lcm < n) {
low = mid + 1;
} else {
high = mid;
}
}
return (int) (low % MOD);
}
private long gcd(long x, long y) {
if (y == 0) return x;
return gcd(y, x % y);
}
private long lcm(long x, long y) {
return x * (y / gcd(x, y));
}
public static void main(String[] args) {
MagicalNumberStream mn = new MagicalNumberStream();
System.out.println(mn.nthMagicalNumber(1, 2, 3)); // 输出 2
System.out.println(mn.nthMagicalNumber(4, 2, 3)); // 输出 6
}
}
解释
方法:
这道题要找出第n个可以被a或b整除的神奇数字。题解使用二分搜索方法,设定搜索区间的左界为min(a, b)(因为这是第一个可能的神奇数字),右界为n * min(a, b)(这是一个保守的上界,理论上第n个神奇数字不会超过这个值)。中点mid在搜索过程中被检查,看通过a或b除以它可以得到多少个神奇数字。这个计数由mid // a + mid // b - mid // lcm(a, b)给出,后者是为了排除重复计数的情况(即同时被a和b整除的情况)。如果这个计数大于或等于n,说明第n个神奇数字小于或等于mid,因此减小右界。否则,增加左界。二分搜索结束后,r+1即是所求的第n个神奇数字。
时间复杂度:
O(log(n))
空间复杂度:
O(1)
代码细节讲解
🦆
为什么在计算第n个神奇数字时选择二分搜索法而不是直接从min(a, b)开始逐个检查?
▷🦆
在计算mid时,为什么要减去`mid // lcm(a, b)`来排除重复计数?请解释其数学原理。
▷🦆
设置搜索区间的右界为`n * min(a, b)`是基于什么考虑?是否有可能这个值小于第n个神奇数字?
▷🦆
二分搜索结束时为什么返回的是`(r + 1) % const`而不直接是`r % const`?
▷