1 题目
题目:丑数II(Ugly Number II)
描述:如果一个数只有质数因子2,3,5 ,那么这个数是一个丑数。前10个丑数分别为 1, 2, 3, 4, 5, 6, 8, 9, 10, 12…设计一个算法,找出第N个丑数。我们可以认为 1 也是一个丑数。
lintcode题号——4,难度——medium
样例1:
输入:n = 9
输出:10
解释:[1,2,3,4,5,6,8,9,10,....],第9个丑数为10。
样例2:
输入:n = 1
输出:1
2 解决方案
2.1 思路
要找第n个丑数,考虑从1开始向后一个个计算出丑数,每次取出最小的数,乘以2、3、5,再对集合中的数排序,再取最小的数乘以2、3、5,以此类推,直到取到第n个数即为结果。需要一个能够放入无序的数,并取出最小的数的数据结构,考虑使用优先队列来解。
还有一种方式,使用3个指针分别表示被乘数2、3、5,初始都指向数组(初始只有数字1),将被指数1与指针表示的值2、3、5分别相乘,取最小的数加入数组,并将对应的指针向右移动一位,以此类推,每次通过被指数与指针表示的数相乘,再取得的最小值,即为下一个丑数的值,重复n次,即可找到第n个丑数。
2.3 时间复杂度
使用优先队列的方式,向优先队列插入和删除元素的耗时为O(log n),从优先队列中获取顶部元素的耗时为O(1),遍历n次,总时间复杂度为O(n * log n)。
使用三指针方式,进行一轮循环即可找到第n个数,时间复杂度为O(n)。
2.4 空间复杂度
使用优先队列的方式,用到了优先队列,空间复杂度为O(n)。
使用三指针的方式,空间复杂度为O(1))。
3 源码
3.1 优先队列方式
细节:
- 当前优先序列从1开始,将当前数乘以2、3、5,并加入最小堆优先序列中。
- 每次弹出顶部元素(当前序列中最小值),用该值乘以2、3、5得到结果放入优先序列中,循环n-1次,此时的优先序列顶部元素即为第n个丑数。
优先队列定义 template<T, Container, Compare> priority_queue;
示例:
priority_queue<int> myQueue; // 最大堆优先序列(省去默认参数)
priority_queue<int, vector<int>, less<int>> myQueue;
// 最大堆优先序列(完整)
priority_queue<int, vector<int>, greater<int>> myQueue; // 最小堆优先序列
扫描二维码关注公众号,回复: 14636905 查看本文章
C++版本:
/**
* @param n: An integer
* @return: return a integer as description.
*/
int nthUglyNumber(int n) {
// write your code here
int result = 1;
if (n == 1)
{
return result;
}
priority_queue<long, vector<long>, greater<long>> numQueue; // 定义最小堆优先队列
numQueue.push(1);
for (int i = 1; i < n; i++)
{
long cur = numQueue.top();
numQueue.push(cur * 2); // 加入最小数与2的乘积
numQueue.push(cur * 3); // 加入最小数与3的乘积
numQueue.push(cur * 5); // 加入最小数与5的乘积
numQueue.pop(); // 弹出前n-1个元素
while (numQueue.top() == cur)
{
numQueue.pop(); // 弹出重复的数
}
}
return numQueue.top(); // 返回优先队列顶部元素
}
3.2 三指针方式
细节:
- 用一个序列nums记录前n个丑数。
- 三个指针index2,index3和index5指向nums中的第一个元素1。
- 最小的丑数只可能出现在index2指向的数的2倍、index3指向的数的3倍和index5指向的数的5倍三者中的一个,找到最小的数后加入序列,并右移一位对应的指针,就能保证生成的丑数是从小到大有序的,循环n轮即可找到第n个丑数。
C++版本:
/**
* @param n: An integer
* @return: return a integer as description.
*/
int nthUglyNumber(int n) {
// write your code here
vector<int> nums;
nums.push_back(1);
int index2 = 0;
int index3 = 0;
int index5 = 0;
while (nums.size() != n)
{
int minValue = min(nums.at(index2) * 2, min(nums.at(index3) * 3, nums.at(index5) * 5));
nums.push_back(minValue);
if (minValue == nums.at(index2) * 2)
{
index2++;
//continue; // 此处不用continue,为了跳过重复的数
}
if (minValue == nums.at(index3) * 3)
{
index3++;
//continue; // 此处不用continue,为了跳过重复的数
}
if (minValue == nums.at(index5) * 5)
{
index5++;
//continue; // 此处不用continue,为了跳过重复的数
}
}
return nums.back();
}