Skip to content

Commit 2e16292

Browse files
committed
feat: add solutions to lc problem: No.1201
No.1201.Ugly Number III
1 parent c20050b commit 2e16292

File tree

7 files changed

+355
-200
lines changed

7 files changed

+355
-200
lines changed

solution/1200-1299/1201.Ugly Number III/README.md

+133-74
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,21 @@
5656

5757
<!-- 这里可写通用的实现逻辑 -->
5858

59-
**方法一:二分搜索**
59+
**方法一:二分查找 + 容斥原理**
6060

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$ 个。
6262

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$ 的丑数个数为:
6464

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)$。
6974

7075
<!-- tabs:start -->
7176

@@ -75,98 +80,152 @@
7580

7681
```python
7782
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-
8283
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
8893
else:
89-
right = mid - 1
90-
return left
94+
l = mid + 1
95+
return l
9196
```
9297

93-
### **Go**
98+
### **Java**
9499

95100
<!-- 这里可写当前语言的特殊实现逻辑 -->
96101

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+
}
110120

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+
}
114124

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+
}
128128
}
129129
```
130130

131131
### **C++**
132132

133-
<!-- 这里可写当前语言的特殊实现逻辑 -->
134-
135133
```cpp
136134
class Solution {
137135
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+
}
145149
}
146-
return x;
150+
return l;
147151
}
148152

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+
}
150156

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);
155159
}
160+
};
161+
```
156162
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;
166217
}
167-
return left;
168218
}
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+
}
170229
```
171230

172231
### **...**

0 commit comments

Comments
 (0)