丑数
1. 概述
- 根据百度百科的解释,丑数有两种定义:
定义一
- 把只包含质因子2,3和5的数称作丑数(Ugly Number)。
定义二
- 给定一个素数集合:
S
=
{
p
1
,
p
2
,
.
.
.
,
p
k
}
S = \{ p_1, p_2, ..., p_k \}
S={p1,p2,...,pk},如果一个数
x
的质因子全部在集合S
中,则x
被称作丑数。
2. 例题
Leetcode 0263 丑数
题目描述:Leetcode 0263 丑数
分析
-
本题的考点:数学。
-
将
n
中所有因子2、3、5
全部除干净,最后判断n
是否等于1
即可。
代码
- C++
class Solution {
public:
bool isUgly(int n) {
if (n <= 0) return false;
while (n % 2 == 0) n /= 2;
while (n % 3 == 0) n /= 3;
while (n % 5 == 0) n /= 5;
return n == 1;
}
};
- Java
class Solution {
public boolean isUgly(int num) {
if (num <= 0) return false;
while (num % 2 == 0) num /= 2;
while (num % 3 == 0) num /= 3;
while (num % 5 == 0) num /= 5;
return num == 1;
}
}
时空复杂度分析
-
时间复杂度: O ( l o g ( n ) ) O(log(n)) O(log(n))。
-
空间复杂度: O ( 1 ) O(1) O(1)。
Leetcode 0264 丑数 II
题目描述:Leetcode 0264 丑数 II
分析
-
本题的考点:归并排序。
-
本题的实质是三路归并。
-
设丑数的集合为
S
,则S
可以分为四个集合的并集,即 S = { 1 } ∪ S 2 ∪ S 3 ∪ S 5 S = \{1\} \cup S_2 \cup S_3 \cup S_5 S={1}∪S2∪S3∪S5,其中 S 2 S_2 S2表示是2
的倍数的所有丑数的集合。 -
我们可以发现 S 2 = 2 × S S_2 = 2 \times S S2=2×S, S 3 = 3 × S S_3 = 3 \times S S3=3×S, S 5 = 5 × S S_5 = 5 \times S S5=5×S。
-
因此我们可以让
S
中初始有一个元素1
,然后让三个指针分别指向1
,然后分别乘以2、3、5
可以得到 S 2 、 S 3 、 S 5 S_2、S_3、S_5 S2、S3、S5中的第一个数,放入S
中,最终就可以得到S
。 -
本题的整体过程:由
S
可以构造 S 2 、 S 3 、 S 5 S_2、S_3、S_5 S2、S3、S5,然后由 S 2 、 S 3 、 S 5 S_2、S_3、S_5 S2、S3、S5又可以构造S
。
代码
- C++
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> q(1, 1);
for (int i = 0, j = 0, k = 0; q.size() < n;) {
int t = min(q[i] * 2, min(q[j] * 3, q[k] * 5));
q.push_back(t);
if (q[i] * 2 == t) i++;
if (q[j] * 3 == t) j++;
if (q[k] * 5 == t) k++;
}
return q.back();
}
};
- Java
class Solution {
public int nthUglyNumber(int n) {
List<Integer> q = new ArrayList<>();
q.add(1);
for (int i = 0, j = 0, k = 0; q.size() < n; ) {
int t = Math.min(q.get(i) * 2, Math.min(q.get(j) * 3, q.get(k) * 5));
q.add(t);
if (q.get(i) * 2 == t) i++;
if (q.get(j) * 3 == t) j++;
if (q.get(k) * 5 == t) k++;
}
return q.get(q.size() - 1);
}
}
时空复杂度分析
-
时间复杂度: O ( n ) O(n) O(n)。
-
空间复杂度: O ( n ) O(n) O(n)。
Leetcode 0313 超级丑数
题目描述:Leetcode 0313 超级丑数
分析
-
本题的考点:多路归并。
-
本题和Leetcode 0264 丑数 II很类似,区别在于本题不一定是三路归并,做法类似。
-
这里使用小顶堆记录多个序列得到的数据,每次从堆顶取出一个数据(设该数据所在的序列为
T
),当这个数据不和生成的丑数序列(设为S
)中的数据重复时,放入丑数序列。之后还需要生成该序列T
中的下一个数据,将取出的数据的生成下一个丑数放入到堆中。 -
为了记录方便,这里堆中存放的是一个
pair
,(first, seond)
表示:(某个序列中的元素,该元素是序列中的第几个元素)。元素从0开始编号。
代码
- C++
class Solution {
public:
int nthSuperUglyNumber(int n, vector<int>& primes) {
// (指针对应的数 * 该指针指向的质数,指针下标)
// 比如:初始,指针都指向0,q[0] = 1, (q[0]*x, 0)
typedef pair<int, int> PII;
priority_queue<PII, vector<PII>, greater<PII>> heap; // 小顶堆
// 刚开始primes.size()个指针下标都指向0
for (int x : primes) heap.push({x, 0});
vector<int> q(n);
q[0] = 1;
for (int i = 1; i < n;) {
auto t = heap.top(); heap.pop();
if (t.first != q[i - 1]) q[i++] = t.first;
int idx = t.second, p = t.first / q[idx]; // 该序列是Sp
heap.push({p * q[idx + 1], idx + 1});
}
return q[n - 1];
}
};
- Java
class Solution {
// (指针对应的数 * 该指针指向的质数,指针下标)
// 比如:初始,指针都指向0,q[0] = 1, (q[0]*x, 0)
static class MyPair implements Comparable<MyPair> {
int x, y;
public MyPair(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public int compareTo(MyPair o) {
return this.x - o.x;
}
}
public int nthSuperUglyNumber(int n, int[] primes) {
Queue<MyPair> pq = new PriorityQueue<>(); // 默认小顶堆
// 刚开始primes.size()个指针下标都指向0
for (int x : primes) pq.add(new MyPair(x, 0));
int[] q = new int[n];
q[0] = 1;
for (int i = 1; i < n; ) {
MyPair t = pq.remove();
if (t.x != q[i - 1]) q[i++] = t.x;
int idx = t.y, p = t.x / q[idx];
pq.add(new MyPair(p * q[idx + 1], idx + 1));
}
return q[n - 1];
}
}
时空复杂度分析
-
时间复杂度: O ( n log ( n ) ) O(n \log(n)) O(nlog(n))。
-
空间复杂度: O ( n ) O(n) O(n)。