
一、最大公约数 最小公倍数 二分 容斥原理 同余原理
1.1 最大公约数 最小公倍数 二分 容斥原理 同余原理
求第n个神奇数字x
, 等价于求小于x的神奇数字有n个
因为x越大,则n越大,即二者为单调递增关系。所以可以二分x,求<=x的n值
对每个x而言,根据容斥定理,n为 x/a + x/b - x/lcm(a,b)
据此二分查找即可: 下界为min(a,b),上界为min(a,b) * n。
需注意二分查找的边界条件,见下文代码注释。
// go
func nthMagicalNumber(n int, a int, b int) int {
lcm := a / gcd(a, b) * b // 最小公倍数, 例如 12 和 18 的最小公倍数为 (12 / 6) * 18, 也可以理解为 (12 / 6) * (18 / 6) * 6
// 左开又开区间 (l, r)
l := 0 // 因为是正整数, 所以 需 > 0, 所以下限为 0
r := min(a, b) * n + 1 // a * n 内有 n 个可被a整除的数字, b * n 内有 n 个可被b整除的数字, 所以 若能被a或b整除, 则因为b的加入会更快的到第n个数, 所以第n个数的上限为 min(a, b) * n + 1, PS: 这个+1可加可不加
for l+1 < r { // 开区间 (l, r)
m := l + (r-l)/2
// 容斥原理 得到 从0...m 有几个 神奇的数字
if m/a + m/b - m/lcm >= n { // 若 >= n 个, 则需记录答案, 并向左找第 n 个
// 这里需包括等号(意为等于时也要向左继续二分)。因为整数的除法可能除不尽,所以不同的mid,却算出相同的mid/a+mid/b-mid/lcm。例如a=2,b=3时,mid为6或7算出的mid/a+mid/b-mid/lcm均为4
r = m
} else { // 若 < n 个, 则需向右找第 n 个
l = m
}
}
return r % (1e9+7) // 注意: 是1e9而不是10e9
}
// 最大公约数
// 如 a = 12, b = 18, gcd = 6
//
// 具体过程如下:
// a, b = 12, 18
// a, b = 6, 12
// a, b = 0, 6
// 返回 b = 6
func gcd(a, b int) int {
// if b == 0 {return a}
// return gcd(b, a % b)
for a != 0 {
a, b = b % a, a
}
return b // a == 0 时, 则只能返回 b 啦, 肯定不能返回 a 啦
}
二、多语言解法
C p p / G o / P y t h o n / R u s t / J s / T s Cpp/Go/Python/Rust/Js/Ts Cpp/Go/Python/Rust/Js/Ts
// cpp
// go 同上
# python
// rust
// js
// ts