解题篇 -- 丑数

题目描述

牛客网:JZ33 丑数
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
示例

输入:7
输出:8
丑数为:[1,2,3,4,5,6,8,9,…],第7个数字是8

题目解析

根据题意,丑数可表示为2x3y5z每个丑数的2、3、5倍数也都是丑数。按从小到大的顺序找到第N个丑数,可以从最小的丑数1开始,推出后续所有丑数。

提醒

  • 通过计算可以发现,丑数前6个数字恰好为[1,2,3,4,5,6],因此可先进行判断
  • 计算过程中会有重复数字出现

方式一:最小堆实现

示例

  • 当前丑数为1,1的倍数:2、3、5也都是丑数,其中最小的是2
  • 当前丑数为2,已知的丑数有3、5(上一轮求得),2的倍数有4、6、10,其中最小的为3
  • 当前丑数为3,已知的丑数有5、4、6、10,3的倍数有6、9、15,最小的为4
  • 当前丑数为4,已知的丑数有5、6、10、9、15,4的倍数有8、12、20,最小的为5
  • 依次类推,直至找到第N个丑数

实现思路

  • 根据思路,每一次查找可找到当前最小的丑数,因此共循环N次可找到第N个丑数
  • 每次计算出的所有丑数都要进行记录,并从中找出最小值,所有用最小堆进行操作
  • 每次计算时会出现重复数字,为保证结果准确性,需要对数字进行去重,使用Set结构
  • 由于数据以乘法的方式递增,后面数字可能会很大,因此队列和集合采用long类型数据
public int GetUglyNumber_Solution(int index) {
        if(index <= 6){
            return index;
        }
        // 每次求得的丑数
        long ans = 0;
        PriorityQueue<Long> pq = new PriorityQueue<>();
        Set<Long> set = new HashSet<>();
        // 1作为最小丑数,初始化最小堆
        pq.offer(1l);
        int[] e = new int[]{2,3,5};
        // 求第N个丑数
        for(int i = 1;i <= index;i++){
            long temp = pq.peek();
            // 堆中加入新的丑数
            for(int j = 0;j < 3;j++){
                if(set.add(temp * e[j])){
                    pq.offer(temp * e[j]);
                }
            }   
            // 弹出当前最小丑数         
            ans = pq.poll();           
        }
        return (int)ans;
    }    

时间复杂度

  • 生成丑数复杂度为O(3n)
  • 每次查重复杂度为O(logn),共O(3nlogn)
  • 维护堆取最小值,插入数为O(log(3n)),最多插入3n次,取出数为O(1),共取n次。

方法二:三指针

参考文章:三指针解法
查找丑数过程中,本质上是依次查找丑数的2、3、5倍数。使用最小堆和集合需要花费时间维护堆和判断重复。
丑数可表示为2x3y5z,因此可以用三个指针分别表示乘以2、3、5的丑数的位置,然后取三者最小值。
得到丑数之后移动指针位置,进行下一轮计算。

示例

  • 当前丑数ans[1] = 1x、y、z初始化为1。下一个丑数可能为ans[x] * 2、ans[y] * 3、ans[z] * 5,其中最小值为2.
  • 当前丑数ans[2] = 2,2可以由ans[x] * 2得到,因此x指针加1,x= 2。下一个丑数可能在ans[x] * 2、ans[y] * 3、ans[z] * 5中,即4、3、5,最小值为3
  • 当前丑数ans[3] = 3,3可以由ans[y] * 3得到,因此y指针加1,y= 2。下一个丑数可能在ans[x] * 2、ans[y] * 3、ans[z] * 5中,即4、6、5,最小值为4
  • 当前丑数ans[4] = 4,4可以由ans[x] * 2得到,因此x指针加1,x= 3。下一个丑数可能在ans[x] * 2、ans[y] * 3、ans[z] * 5中,即6、6、5,最小值为5
  • 当前丑数ans[5] = 5,5可以由ans[z] * 5得到,因此z指针加1,z= 2。下一个丑数可能在ans[x] * 2、ans[y] * 3、ans[z] * 5中,即6、6、10,最小值为6
  • 当前丑数ans[6] = 6,6可以由ans[x] * 2得到,因此x指针加1,x= 4,也可以由ans[y] * 3得到,因此y指针加1,y = 3。下一个丑数可能在ans[x] * 2、ans[y] * 3、ans[z] * 5中,即8、9、10,最小值为8
  • 以此类推,可计算出第N个丑数
public int GetUglyNumber_Solution(int index) {
        if(index <= 6){
            return index;
        }
        // 存储丑数结果,从下标从1开始
        int[] ans = new int[index + 1];
        // 第一个丑数为1
        ans[1] = 1;
        // 指针分别为指向下一个2,3,5可能成为下一个丑数的数的位置
        int x = 1,y = 1,z = 1;
        for(int i = 2;i <= index;i++){
            ans[i] = Math.min(ans[x] * 2,Math.min(ans[y] * 3,ans[z] * 5));
            // 丑数可由ans[x] * 2 得到,指针后移 
            if(ans[x] * 2 == ans[i]){
                x++;
            }
            if(ans[y] * 3 == ans[i]){
                y++;
            }
            if(ans[z] * 5 == ans[i]){
                z++;
            }
        }
        return ans[index];
    }    
  • 时间复杂度:O(n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CISP-PTT是指中国信息安全测评评测员的资格认证,主要针对信息安全领域的专业人士。CISP-PTT的解题部分是考试中的一个重要环节,主要是评估考生在解决现实信息安全问题能力的能力。 CISP-PTT解题考试要求考生具备系统分析和问题解决的能力。考试中,考生将面对一系列实际的信息安全问题,并需要通过对问题的分析和解决方案的设计来展示他们的能力。 在解题过程中,考生需要遵循一定的方法和步骤,首先要对问题进行全面的分析,了解问题的背景和关键因素。然后,需要提出合理的解决方案,包括技术和管理层面的措施。重要的是要确保解决方案的可行性和有效性,以及其对整个系统的影响。 在解题环节中,考生的思维和创新能力至关重要。他们需要灵活运用自己的知识和经验,找到问题的关键点,并提出切实可行的解决方案。同时,要能够正确地分析问题,并考虑到可能的风险和威胁。 此外,在解题过程中,考生需要具备清晰的表达和沟通能力,能够将解决方案准确地传达给评审人员。他们还需要在规定的时间内完成解决方案的设计和书写,因此时间管理和组织能力也是非常重要的。 总之,CISP-PTT解题考试要求考生通过分析实际的信息安全问题,提出解决方案,并能够清晰地表达和传达自己的思路。这是评估他们在信息安全领域解决问题能力的重要环节。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值