|
56 | 56 |
|
57 | 57 | <!-- 这里可写通用的实现逻辑 -->
|
58 | 58 |
|
59 |
| -**方法一:二分搜索** |
| 59 | +**方法一:二分查找 + 容斥原理** |
60 | 60 |
|
61 |
| -根据题目提示结果在 [1, 2 * 10<sup>9</sup>] 的闭区间上,所以定义二分搜索的左边界 left=1,右边界 right=2e9。此时我们只需要在 [left,right] 的闭区间内找到一个最小的数 num,使其满足 [1,num] 内的丑数总数等于 n,则 num 就是第 n 个丑数。计算在 [1,num] 的范围内丑数的数目,即可以被 a、b 或 c 任意一个数整除的数的总数,其方法如下: |
| 61 | +我们可以将题目转换为:找到最小的正整数 $x$,使得小于等于 $x$ 的丑数个数恰好为 $n$ 个。 |
62 | 62 |
|
63 |
| -`f(num, a, b, c) = num/a + num/b + num/c - a⋂b - a⋂c - b⋂c + a⋂b⋂c` |
| 63 | +对于一个正整数 $x$,能被 $a$ 整除的数有 $\left\lfloor \frac{x}{a} \right\rfloor$ 个,能被 $b$ 整除的数有 $\left\lfloor \frac{x}{b} \right\rfloor$ 个,能被 $c$ 整除的数有 $\left\lfloor \frac{x}{c} \right\rfloor$ 个,能被 $a$ 和 $b$ 同时整除的数有 $\left\lfloor \frac{x}{lcm(a, b)} \right\rfloor$ 个,能被 $a$ 和 $c$ 同时整除的数有 $\left\lfloor \frac{x}{lcm(a, c)} \right\rfloor$ 个,能被 $b$ 和 $c$ 同时整除的数有 $\left\lfloor \frac{x}{lcm(b, c)} \right\rfloor$ 个,能被 $a$, $b$ 和 $c$ 同时整除的数有 $\left\lfloor \frac{x}{lcm(a, b, c)} \right\rfloor$ 个。根据容斥原理,小于等于 $x$ 的丑数个数为: |
64 | 64 |
|
65 |
| -- num/a 表示在 [1,num] 内可以整除 a 的数目,num/b 表示在 [1,num] 内可以整除 b 的数目,num/c 表示在 [1,num] 内可以整除 c 的数目。 |
66 |
| -- a⋂b 表示在 [1,num] 内可以同时整除 a 和 b 的数目,a⋂c 表示在 [1,num] 内可以同时整除 a 和 c 的数,b⋂c 表示在 [1,num] 内可以同时整除 b 和 c 的数。 |
67 |
| -- a⋂b⋂c 表示在 [1,num] 内可以同时整除 a、b 和 c 的数。 |
68 |
| -- a⋂b = num/least_common_multiple(a, b),其他情况依次类推。 |
| 65 | +$$ |
| 66 | +\left\lfloor \frac{x}{a} \right\rfloor + \left\lfloor \frac{x}{b} \right\rfloor + \left\lfloor \frac{x}{c} \right\rfloor - \left\lfloor \frac{x}{lcm(a, b)} \right\rfloor - \left\lfloor \frac{x}{lcm(a, c)} \right\rfloor - \left\lfloor \frac{x}{lcm(b, c)} \right\rfloor + \left\lfloor \frac{x}{lcm(a, b, c)} \right\rfloor |
| 67 | +$$ |
| 68 | + |
| 69 | +我们可以使用二分查找的方法找到最小的正整数 $x$,使得小于等于 $x$ 的丑数个数恰好为 $n$ 个。 |
| 70 | + |
| 71 | +定义二分查找的左边界为 $l=1$,右边界为 $r=2 \times 10^9$,其中 $2 \times 10^9$ 是题目给定的最大值。在二分查找的每一步中,我们找出中间数 $mid$,如果小于等于 $mid$ 的丑数个数大于等于 $n$,那么说明最小的正整数 $x$ 落在 $[l,mid]$ 区间内,否则落在 $[mid+1,r]$ 区间内。在二分查找的过程中,我们需要不断更新小于等于 $mid$ 的丑数个数,直到找到最小的正整数 $x$。 |
| 72 | + |
| 73 | +时间复杂度 $O(\log m)$,其中 $m = 2 \times 10^9$。空间复杂度 $O(1)$。 |
69 | 74 |
|
70 | 75 | <!-- tabs:start -->
|
71 | 76 |
|
|
75 | 80 |
|
76 | 81 | ```python
|
77 | 82 | class Solution:
|
78 |
| - def f(self, num: int, a: int, b: int, c: int) -> int: |
79 |
| - return num // a + num // b + num // c - num // math.lcm(a, b) - num // math.lcm(a, c) - num // math.lcm(b, c) \ |
80 |
| - + num // math.lcm(a, b, c) |
81 |
| - |
82 | 83 | def nthUglyNumber(self, n: int, a: int, b: int, c: int) -> int:
|
83 |
| - left, right = 1, int(2e9) |
84 |
| - while left <= right: |
85 |
| - mid = left + (right - left) // 2 |
86 |
| - if self.f(mid, a, b, c) < n: |
87 |
| - left = mid + 1 |
| 84 | + ab = lcm(a, b) |
| 85 | + bc = lcm(b, c) |
| 86 | + ac = lcm(a, c) |
| 87 | + abc = lcm(a, b, c) |
| 88 | + l, r = 1, 2 * 10**9 |
| 89 | + while l < r: |
| 90 | + mid = (l + r) >> 1 |
| 91 | + if mid // a + mid // b + mid // c - mid // ab - mid // bc - mid // ac + mid // abc >= n: |
| 92 | + r = mid |
88 | 93 | else:
|
89 |
| - right = mid - 1 |
90 |
| - return left |
| 94 | + l = mid + 1 |
| 95 | + return l |
91 | 96 | ```
|
92 | 97 |
|
93 |
| -### **Go** |
| 98 | +### **Java** |
94 | 99 |
|
95 | 100 | <!-- 这里可写当前语言的特殊实现逻辑 -->
|
96 | 101 |
|
97 |
| -```go |
98 |
| -func nthUglyNumber(n int, a int, b int, c int) int { |
99 |
| - left, right := 1, int(2e9) |
100 |
| - for left <= right { |
101 |
| - mid := left + (right-left)/2 |
102 |
| - if f(mid, a, b, c) < n { |
103 |
| - left = mid + 1 |
104 |
| - } else { |
105 |
| - right = mid - 1 |
106 |
| - } |
107 |
| - } |
108 |
| - return left |
109 |
| -} |
| 102 | +```java |
| 103 | +class Solution { |
| 104 | + public int nthUglyNumber(int n, int a, int b, int c) { |
| 105 | + long ab = lcm(a, b); |
| 106 | + long bc = lcm(b, c); |
| 107 | + long ac = lcm(a, c); |
| 108 | + long abc = lcm(ab, c); |
| 109 | + long l = 1, r = 2000000000; |
| 110 | + while (l < r) { |
| 111 | + long mid = (l + r) >> 1; |
| 112 | + if (mid / a + mid / b + mid / c - mid / ab - mid / bc - mid / ac + mid / abc >= n) { |
| 113 | + r = mid; |
| 114 | + } else { |
| 115 | + l = mid + 1; |
| 116 | + } |
| 117 | + } |
| 118 | + return (int) l; |
| 119 | + } |
110 | 120 |
|
111 |
| -func f(num int, a int, b int, c int) int { |
112 |
| - return num/a + num/b + num/c - num/lcm(a, b) - num/lcm(a, c) - num/lcm(b, c) + num/lcm(lcm(a, b), c) |
113 |
| -} |
| 121 | + private long gcd(long a, long b) { |
| 122 | + return b == 0 ? a : gcd(b, a % b); |
| 123 | + } |
114 | 124 |
|
115 |
| -// Least common multiple |
116 |
| -func lcm(a, b int) int { |
117 |
| - // Greatest common divisor |
118 |
| - gcd := func(x, y int) int { |
119 |
| - for y != 0 { |
120 |
| - if x < y { |
121 |
| - x, y = y, x |
122 |
| - } |
123 |
| - x, y = y, x%y |
124 |
| - } |
125 |
| - return x |
126 |
| - } |
127 |
| - return a * b / gcd(a, b) |
| 125 | + private long lcm(long a, long b) { |
| 126 | + return a * b / gcd(a, b); |
| 127 | + } |
128 | 128 | }
|
129 | 129 | ```
|
130 | 130 |
|
131 | 131 | ### **C++**
|
132 | 132 |
|
133 |
| -<!-- 这里可写当前语言的特殊实现逻辑 --> |
134 |
| - |
135 | 133 | ```cpp
|
136 | 134 | class Solution {
|
137 | 135 | public:
|
138 |
| - long gcd(long x, long y) { |
139 |
| - while (y != 0) { |
140 |
| - if (x < y) |
141 |
| - swap(x, y); |
142 |
| - long tmp = x % y; |
143 |
| - x = y; |
144 |
| - y = tmp; |
| 136 | + int nthUglyNumber(int n, int a, int b, int c) { |
| 137 | + long long ab = lcm(a, b); |
| 138 | + long long bc = lcm(b, c); |
| 139 | + long long ac = lcm(a, c); |
| 140 | + long long abc = lcm(ab, c); |
| 141 | + long long l = 1, r = 2000000000; |
| 142 | + while (l < r) { |
| 143 | + long long mid = (l + r) >> 1; |
| 144 | + if (mid / a + mid / b + mid / c - mid / ab - mid / bc - mid / ac + mid / abc >= n) { |
| 145 | + r = mid; |
| 146 | + } else { |
| 147 | + l = mid + 1; |
| 148 | + } |
145 | 149 | }
|
146 |
| - return x; |
| 150 | + return l; |
147 | 151 | }
|
148 | 152 |
|
149 |
| - long lcm(long x, long y) { return x * y / gcd(x, y); } |
| 153 | + long long lcm(long long a, long long b) { |
| 154 | + return a * b / gcd(a, b); |
| 155 | + } |
150 | 156 |
|
151 |
| - long f(int num, int a, int b, int c) { |
152 |
| - long sumabc = long(num / a) + num / b + num / c; |
153 |
| - long intersections = long(num / lcm(a, b)) + num / lcm(a, c) + num / lcm(b, c) - num / lcm(lcm(a, b), c); |
154 |
| - return sumabc - intersections; |
| 157 | + long long gcd(long long a, long long b) { |
| 158 | + return b == 0 ? a : gcd(b, a % b); |
155 | 159 | }
|
| 160 | +}; |
| 161 | +``` |
156 | 162 |
|
157 |
| - int nthUglyNumber(int n, int a, int b, int c) { |
158 |
| - int left = 1, right = int(2e9); |
159 |
| - while (left <= right) { |
160 |
| - int mid = left + (right - left) / 2; |
161 |
| - if (f(mid, a, b, c) < n) { |
162 |
| - left = mid + 1; |
163 |
| - } else { |
164 |
| - right = mid - 1; |
165 |
| - } |
| 163 | +### **Go** |
| 164 | +
|
| 165 | +```go |
| 166 | +func nthUglyNumber(n int, a int, b int, c int) int { |
| 167 | + ab, bc, ac := lcm(a, b), lcm(b, c), lcm(a, c) |
| 168 | + abc := lcm(ab, c) |
| 169 | + var l, r int = 1, 2e9 |
| 170 | + for l < r { |
| 171 | + mid := (l + r) >> 1 |
| 172 | + if mid/a+mid/b+mid/c-mid/ab-mid/bc-mid/ac+mid/abc >= n { |
| 173 | + r = mid |
| 174 | + } else { |
| 175 | + l = mid + 1 |
| 176 | + } |
| 177 | + } |
| 178 | + return l |
| 179 | +} |
| 180 | +
|
| 181 | +func gcd(a, b int) int { |
| 182 | + if b == 0 { |
| 183 | + return a |
| 184 | + } |
| 185 | + return gcd(b, a%b) |
| 186 | +} |
| 187 | +
|
| 188 | +func lcm(a, b int) int { |
| 189 | + return a * b / gcd(a, b) |
| 190 | +} |
| 191 | +``` |
| 192 | + |
| 193 | +### **TypeScript** |
| 194 | + |
| 195 | +```ts |
| 196 | +function nthUglyNumber(n: number, a: number, b: number, c: number): number { |
| 197 | + const ab = lcm(BigInt(a), BigInt(b)); |
| 198 | + const bc = lcm(BigInt(b), BigInt(c)); |
| 199 | + const ac = lcm(BigInt(a), BigInt(c)); |
| 200 | + const abc = lcm(BigInt(a), bc); |
| 201 | + let l = 1n; |
| 202 | + let r = BigInt(2e9); |
| 203 | + while (l < r) { |
| 204 | + const mid = (l + r) >> 1n; |
| 205 | + const count = |
| 206 | + mid / BigInt(a) + |
| 207 | + mid / BigInt(b) + |
| 208 | + mid / BigInt(c) - |
| 209 | + mid / ab - |
| 210 | + mid / bc - |
| 211 | + mid / ac + |
| 212 | + mid / abc; |
| 213 | + if (count >= BigInt(n)) { |
| 214 | + r = mid; |
| 215 | + } else { |
| 216 | + l = mid + 1n; |
166 | 217 | }
|
167 |
| - return left; |
168 | 218 | }
|
169 |
| -}; |
| 219 | + return Number(l); |
| 220 | +} |
| 221 | + |
| 222 | +function gcd(a: bigint, b: bigint): bigint { |
| 223 | + return b === 0n ? a : gcd(b, a % b); |
| 224 | +} |
| 225 | + |
| 226 | +function lcm(a: bigint, b: bigint): bigint { |
| 227 | + return (a * b) / gcd(a, b); |
| 228 | +} |
170 | 229 | ```
|
171 | 230 |
|
172 | 231 | ### **...**
|
|
0 commit comments