1. 问题描述:
请你帮忙设计一个程序,用来找出第 n
个丑数。丑数是可以被 a
或 b
或 c
整除的 正整数。
示例 1:
输入:n = 3, a = 2, b = 3, c = 5
输出:4
解释:丑数序列为 2, 3, 4, 5, 6, 8, 9, 10... 其中第 3 个是 4。
示例 2:
输入:n = 4, a = 2, b = 3, c = 4
输出:6
解释:丑数序列为 2, 3, 4, 6, 8, 9, 10, 12... 其中第 4 个是 6。
示例 3:
输入:n = 5, a = 2, b = 11, c = 13
输出:10
解释:丑数序列为 2, 4, 6, 8, 10, 11, 12, 13... 其中第 5 个是 10。
示例 4:
输入:n = 1000000000, a = 2, b = 217983653, c = 336916467
输出:1999999984
提示:
1 <= n, a, b, c <= 10^9
1 <= a * b * c <= 10^18
- 本题结果在
[1, 2 * 10^9]
的范围内
2. 思路分析:
① 这道题目实际上考察的是二分查找与容斥定理的结合,首先我们需要理解容斥定理的概念,[0, x]能够被a整除的的个数为x / a, 能够被b整除的为x / b,能够被c整除的是x / c,能够被a与b同时整除的为a * b // gcd(a, b)也就是求解出a, b 的最小公倍数,同理同时被b,c整除的为b * c // gcd(b, c)其余的也是类似的,结合容斥定理我们可以知道在[0, x]范围内的能够被a或者b或者c整除的个数的公式为:count = mid // a + mid // b + mid // c - mid // lcm_a_b - mid // lcm_b_c - mid // lcm_a_c + mid // lcm_a_b_c,我们可知道最小的范围肯定是三个数字的最小值,最大范围可以是三个数字最小值的n倍(最小值的n倍就至少包含了n个丑数了而另外增加了两个变量所以丑数的数量会更多所以这个最大范围为最小值得n倍就够了),确定好了范围之后那么使用二分查找即可确定丑数的范围了
② 需要注意的是二分查找的结果并不是最后的答案,因为丑数是可能分布在x + min(min(res % a, res % b) , res % c)的范围之内的,这个是因为在二分查找的过程中有可能当前的x都不是a,b,c的倍数,但是丑数的个数还是这样多个,所以需要减去 min(min(res % a, res % b) , res % c)才是最终的答案,二分查找的时候只是落在了丑数的范围之内,比如这样的数据: 4 2 3 4预期结果是6但是结果没有减去返回的结果是7
③ 弄清楚了容斥定理结合二分查找还是比较容易理解的
3. 代码如下:
from math import gcd
class Solution:
# https://leetcode-cn.com/problems/ugly-number-iii/solution/er-fen-fa-si-lu-pou-xi-by-alfeim/
def nthUglyNumber(self, n: int, a: int, b: int, c: int) -> int:
# 二分查找
def binarySearch(low, high, a, b, c, n):
if low >= high: return low
mid = (low + high) // 2
lcm_a_b = a * b // gcd(a, b)
lcm_a_c = a * c // gcd(a, c)
lcm_b_c = b * c // gcd(b, c)
lcm_a_b_c = lcm_a_b * c // gcd(lcm_a_b, c)
# 容斥定理:能够被a或b或者c想除的数目
count = mid // a + mid // b + mid // c - mid // lcm_a_b - mid // lcm_b_c - mid // lcm_a_c + mid // lcm_a_b_c
if count == n: return mid
if count < n: return binarySearch(mid + 1, high, a, b, c, n)
return binarySearch(low, mid - 1, a, b, c, n)
# 左边界为三个值中的最小值
low = min(min(a, b), c)
# 最大值为最小值的n倍
high = low * n
res = binarySearch(low, high, a, b, c, n)
left_a = res % a
left_b = res % b
left_c = res % c
return res - min(min(left_a, left_b), left_c)