这里是题目描述:剑指Offer-面试题49:丑数
对于一个丑数,它能且只能被2、3、5整除,也就是说,丑数可以被除以2、3、5中的一个或几个数,最终得到0。对于本题,直观的方法是从i=1
开始,判断每个数字能否被2、3、5整除最后得到1,如果可以,i就是丑数,找到的丑数个数+1,否则不是丑数,接着判断下一个数字i
。这种方法固然没有错误,但是i
不管是不是丑数,都需要经过计算判断,因此要寻找第n
个丑数,n
较大时,会超出时间复杂度。
因此我们需要一种只计算丑数的方法。根据丑数的性质,我们可以得知丑数只能是由1和2、3、5相乘得到,因此一个丑数可以由已经找到的其他丑数乘2、3、5得到。那么我们如何按顺序从小到大得到指定的第i
个丑数呢?
方法一:使用若干辅助队列
我们建立三个队列,分别存储已经找到的丑数乘2、3、5后的结果作为候选丑数。1是找到的第一个丑数,将它和2、3、5相乘得到的值2、3、5分别存入三个队列。因为先找到的丑数一定小于后找到的丑数,而队列又拥有先进先出的特性,因此每个队列的队头都是整个队列的最小数字,再从三个队列的队头中选出最小值,作为找到的下一个丑数(注意:如果三个队头的最小值和刚刚找到的最小值相同,则跳过,直接再次选取最小值)
题解代码:
class Solution {
public int nthUglyNumber(int n) {
//分别存储一个丑数乘以2、3、5的结果,一定是以从小到大的顺序存储的
Queue<Integer> queue2=new LinkedList<>();
Queue<Integer> queue3=new LinkedList<>();
Queue<Integer> queue5=new LinkedList<>();
int findedNum=1;
int curUgly=1;
queue2.offer(curUgly*2);
queue3.offer(curUgly*3);
queue5.offer(curUgly*5);
while(findedNum<n)
{
int temp;
if(queue2.peek()<queue3.peek())
{
if(queue2.peek()<queue5.peek())
{
temp=queue2.poll();
}
else
{
temp=queue5.poll();
}
}
else if(queue3.peek()<queue5.peek())
{
temp=queue3.poll();
}
else
{
temp=queue5.poll();
}
if(temp==curUgly)
{
continue;
}
findedNum++;
curUgly=temp;
queue2.offer(curUgly*2);
queue3.offer(curUgly*3);
queue5.offer(curUgly*5);
}
return curUgly;
}
}
时间复杂度:O(n)
空间复杂度:O(n)
本题中,因为丑数的定义时只包含因子2、3和5的数,因此我们建立了三个队列存储已经找到的丑数和2、3、5相乘的结果作为候选丑数。我们可以同样的原理进行扩展,对于Leetcode-313.超级丑数,假设超级丑数有k个因子,我们可以建立k个队列存储候选丑数,再从所有队头中选出最小值作为下一个超级丑数
方法二:使用堆
我们可以将方法一中的三个队列改成一个小顶堆,已经找到丑数和2、3、5相乘的结果都插入这个小顶堆当中,一次插入操作的平均时间开销是O(logn),而堆顶就是要寻找的下一个丑数,找到下一个丑数的时间开销为O(1)。
因此,使用堆找到第n个丑数的时间复杂度:O(nlogn),空间复杂度:O(n)
方法三:三指针法
《剑指Offer》这本书还为本题提供了提供了一种解法:假设我们按顺序已经找到了若干个丑数,最大的丑数是M。如何找到M之后的下一个丑数?
我们使用三个指针 T2 T3 T5 来指向已经找到的丑数中的某一个,被指向的丑数满足: T2 指向丑数 M2,所有比 M2 小的丑数与2的乘积都不大于当前找到的最大丑数M,只有 M2 与2的乘积刚好大于M; M3 和 M5 同理。再从 M2x2、 M3x3、 M5x5中选出最小的,加入M后面,被选中的指针Tx前进一位。
初始情况:1是已经被找到的丑数,M=1,T2 T3 T5都指向1
时间复杂度:O(n)
空间复杂度:O(n)
相似题目:
题解代码:
class Solution {
public int getKthMagicNumber(int k) {
if(k==1)
{
return 1;
}
Queue<Integer> queue3=new LinkedList<>(); //用于记录待选择数的队列,分别记录已被选择数字和3、5、7相乘的结果
Queue<Integer> queue5=new LinkedList<>();
Queue<Integer> queue7=new LinkedList<>();
int num=1; //记录已有多少个数据
int res=1;
queue3.offer(res*3);
queue5.offer(res*5);
queue7.offer(res*7);
while(num<k)
{
int temp=-1;
if(queue3.peek()<queue5.peek())
{
if(queue3.peek()<queue7.peek())
{
temp=queue3.remove();
}
else
{
temp=queue7.remove();
}
}
else if(queue5.peek()<queue7.peek())
{
temp=queue5.remove();
}
else
{
temp=queue7.remove();
}
if(res<temp)
{
res=temp;
num++;
queue3.offer(res*3);
queue5.offer(res*5);
queue7.offer(res*7);
}
}
return res;
}
}