Skip to content

Commit c427f1c

Browse files
committed
264
1 parent c7d4b49 commit c427f1c

File tree

2 files changed

+208
-0
lines changed

2 files changed

+208
-0
lines changed

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,5 @@
226226
* [258. Add Digits](leetcode-258-Add-Digits.md)
227227
* [260. Single Number III](leetcode-260-Single-NumberIII.md)
228228
* [263. Ugly Number](leetcode-263-Ugly-Number.md)
229+
* [264. Ugly Number II](leetcode-264-Ugly-NumberII.md)
229230
* [更多](more.md)

leetcode-264-Ugly-NumberII.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# 题目描述(中等难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/264.jpg)
4+
5+
输出第 `n` 个丑数。
6+
7+
# 解法一 暴力
8+
9+
判断每个数字是否是丑数,然后数到第 `n` 个。
10+
11+
```java
12+
public int nthUglyNumber(int n) {
13+
int count = 0;
14+
int result = 1;
15+
while (count < n) {
16+
if (isUgly(result)) {
17+
count++;
18+
}
19+
result++;
20+
}
21+
//result 多加了 1
22+
return result - 1;
23+
}
24+
25+
public boolean isUgly(int num) {
26+
if (num <= 0) {
27+
return false;
28+
}
29+
while (num % 2 == 0) {
30+
num /= 2;
31+
}
32+
while (num % 3 == 0) {
33+
num /= 3;
34+
}
35+
while (num % 5 == 0) {
36+
num /= 5;
37+
}
38+
return num == 1;
39+
}
40+
```
41+
42+
不过题目没有那么简单,这样的话会超时。
43+
44+
受到 [204 题](https://leetcode.wang/leetcode-204-Count-Primes.html) 求小于 `n` 的素数个数的启发,我们这里考虑一下筛选法。先把当时的思路粘贴过来。
45+
46+
> 用一个数组表示当前数是否是素数。
47+
>
48+
> 然后从 `2` 开始,将 `2` 的倍数,`4``6``8``10` ...依次标记为非素数。
49+
>
50+
> 下个素数 `3`,将 `3` 的倍数,`6``9``12``15` ...依次标记为非素数。
51+
>
52+
> 下个素数 `7`,将 `7` 的倍数,`14``21``28``35` ...依次标记为非素数。
53+
>
54+
> 在代码中,因为数组默认值是 `false` ,所以用 `false` 代表当前数是素数,用 `true` 代表当前数是非素数。
55+
56+
下边是当时的代码。
57+
58+
```java
59+
public int countPrimes(int n) {
60+
boolean[] notPrime = new boolean[n];
61+
int count = 0;
62+
for (int i = 2; i < n; i++) {
63+
if (!notPrime[i]) {
64+
count++;
65+
//将当前素数的倍数依次标记为非素数
66+
for (int j = 2; j * i < n; j++) {
67+
notPrime[j * i] = true;
68+
}
69+
}
70+
}
71+
return count;
72+
}
73+
```
74+
75+
这里的话,所有丑数都是之前的丑数乘以 `2, 3, 5` 生成的,所以我们也可以提前把后边的丑数标记出来。这样的话,就不用调用 `isUgly` 函数判断当前是否是丑数了。
76+
77+
```java
78+
public int nthUglyNumber(int n) {
79+
HashSet<Integer> set = new HashSet<>();
80+
int count = 0;
81+
set.add(1);
82+
int result = 1;
83+
while (count < n) {
84+
if (set.contains(result)) {
85+
count++;
86+
set.add(result * 2);
87+
set.add(result * 3);
88+
set.add(result * 5);
89+
}
90+
result++;
91+
}
92+
return result - 1;
93+
}
94+
```
95+
96+
但尴尬的是,依旧是超时,悲伤。然后就去看题解了,分享一下别人的解法。
97+
98+
# 解法二
99+
100+
参考 [这里](https://leetcode.com/problems/ugly-number-ii/discuss/69372/Java-solution-using-PriorityQueue)
101+
102+
看一下解法一中 `set` 的方法,我们递增 `result`,然后看 `set` 中是否含有。如果含有的话,就把当前数乘以 `2, 3, 5` 继续加到 `set` 中。
103+
104+
因为 `result` 是递增的,所以我们每次找到的其实是 `set` 中最小的元素。
105+
106+
所以我们不需要一直递增 `result` ,只需要每次找 `set` 中最小的元素。找最小的元素,就可以想到优先队列了。
107+
108+
还需要注意一点,当我们从 `set` 中拿到最小的元素后,要把这个元素以及和它相等的元素都删除。
109+
110+
```java
111+
public int nthUglyNumber(int n) {
112+
Queue<Long> queue = new PriorityQueue<Long>();
113+
int count = 0;
114+
long result = 1;
115+
queue.add(result);
116+
while (count < n) {
117+
result = queue.poll();
118+
// 删除重复的
119+
while (!queue.isEmpty() && result == queue.peek()) {
120+
queue.poll();
121+
}
122+
count++;
123+
queue.offer(result * 2);
124+
queue.offer(result * 3);
125+
queue.offer(result * 5);
126+
}
127+
return (int) result;
128+
}
129+
```
130+
131+
这里的话要用 `long`,不然的话如果溢出,可能会将一个负数加到队列中,最终结果也就不会准确了。
132+
133+
我们还可以用是 `TreeSet` ,这样就不用考虑重复元素了。
134+
135+
```java
136+
public int nthUglyNumber(int n) {
137+
TreeSet<Long> set = new TreeSet<Long>();
138+
int count = 0;
139+
long result = 1;
140+
set.add(result);
141+
while (count < n) {
142+
result = set.pollFirst();
143+
count++;
144+
set.add(result * 2);
145+
set.add(result * 3);
146+
set.add(result * 5);
147+
}
148+
return (int) result;
149+
}
150+
```
151+
152+
# 解法三
153+
154+
参考 [这里](https://leetcode.com/problems/ugly-number-ii/discuss/69362/O(n)-Java-solution)
155+
156+
我们知道丑数序列是 `1, 2, 3, 4, 5, 6, 8, 9...`
157+
158+
我们所有的丑数都是通过之前的丑数乘以 `2, 3, 5` 生成的,所以丑数序列可以看成下边的样子。
159+
160+
`1, 1×2, 1×3, 2×2, 1×5, 2×3, 2×4, 3×3...`
161+
162+
我们可以把丑数分成三组,用丑数序列分别乘 `2, 3, 5`
163+
164+
```java
165+
2: 1×2, 2×2, 3×2, 4×2, 5×2, 6×2, 8×2,9×2,…
166+
3: 1×3, 2×3, 3×3, 4×3, 5×3, 6×3, 8×3,9×3,…
167+
5: 1×5, 2×5, 3×5, 4×5, 5×5, 6×5, 8×5,9×5,…
168+
```
169+
170+
我们需要做的就是把上边三组按照顺序合并起来。
171+
172+
合并有序数组的话,可以通过归并排序的思想,利用三个指针,每次找到三组中最小的元素,然后指针后移。
173+
174+
当然,最初我们我们并不知道丑数序列,我们可以一边更新丑数序列,一边使用丑数序列。
175+
176+
```java
177+
public int nthUglyNumber(int n) {
178+
int[] ugly = new int[n];
179+
ugly[0] = 1; // 丑数序列
180+
int index2 = 0, index3 = 0, index5 = 0; //三个指针
181+
for (int i = 1; i < n; i++) {
182+
// 三个中选择较小的
183+
int factor2 = 2 * ugly[index2];
184+
int factor3 = 3 * ugly[index3];
185+
int factor5 = 5 * ugly[index5];
186+
int min = Math.min(Math.min(factor2, factor3), factor5);
187+
ugly[i] = min;//更新丑数序列
188+
if (factor2 == min)
189+
index2++;
190+
if (factor3 == min)
191+
index3++;
192+
if (factor5 == min)
193+
index5++;
194+
}
195+
return ugly[n - 1];
196+
}
197+
```
198+
199+
这里需要注意的是,归并排序中我们每次从两个数组中选一个较小的,所以用的是 `if...else...`
200+
201+
这里的话,用的是并列的 `if` , 这样如果有多组的当前值都是 `min`,指针都需要后移,从而保证 `ugly` 数组中不会加入重复元素。
202+
203+
#
204+
205+
解法二的话自己其实差一步就可以想到了。
206+
207+
解法三又是先通过分类,然后有一些动态规划的思想,用之前的解更新当前的解。

0 commit comments

Comments
 (0)