剑指Offer-面试题49:丑数

这里是题目描述:剑指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)

相似题目:

LeetCode-面试题 17.09. 第 k 个数

题解代码:

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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值