【剑指offer】丑数

🔥题目

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

输入:20
输出:36
解释:前20个丑数为 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 36。

 

☘️解析

解法1:最小堆

使用一个最小堆(优先队列),先加入一个1进行初始化。

每次取堆顶元素x(x是当前堆中最小的丑数),然后把丑数 2x、3x、5x 加入堆中。那么,当你第n次poll的时候,取出的就是第n个丑数啦!

但注意,上述操作会向堆中加入重复的元素。我们还需要使用一个哈希集合避免重复。

解法2:三指针

定义三个指针,它们彼此独立的指向已有的丑数数组。

要求第n个丑数(此时前n-1个丑数的数组已存在),求出三个候选数,最小值就是第n个丑数。这三个候选数分别根据三个指针求出:nums[p2] * 2nums[p3] * 3nums[p5] * 5

如果一个候选数称为了第i个丑数,那么其对应的指针+1。接着进行下一次候选比拼,求第n+1个丑数…

思路大概就是这样,其实这是一个动态规划,称为三指针也是比较生动贴切的。

 

🧊代码

解法1:最小堆

class Solution {
    public int nthUglyNumber(int n) {
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        Set<Integer> set = new HashSet<>();
        heap.offer(1);
        set.add(1);
        int[] items = {2, 3, 5};
        
        for (int i = 0; i < n - 1; i++) {
            int ugly = heap.poll();
            for (int item : items) {
                int next = ugly * item;
                // 下面这个写法有点儿妙
                if (set.add(next)) {
                    heap.offer(next);
                }
            }
        }
        return heap.poll();
    }
}

解法2:三指针

class Solution {
    public int nthUglyNumber(int n) {
        int[] nums = new int[n];
        nums[0] = 1;
        int p2 = 0;
        int p3 = 0;
        int p5 = 0;
        for (int i = 1; i < n; i++) {
            int num2 = nums[p2] * 2;
            int num3 = nums[p3] * 3;
            int num5 = nums[p5] * 5;
            nums[i] = Math.min(num2, Math.min(num3, num5));
            if (nums[i] == num2) {
                p2++;
            }
            if (nums[i] == num3) {
                p3++;
            }
            if (nums[i] == num5) {
                p5++;
            }
        }
        return nums[n - 1];
    }
}

 

🌸补充

最小堆法时间复杂度:O(nlogn) 。找第n个丑数需要从堆里取n次,向堆中放3n次(如果不考虑细节),取n次是 O(n) 的复杂度,放的时候需要在堆中自动排序,是 O(3logn) —— 合起来看时间复杂度是 O(n * 3logn) = O(nlogn) 。

三指针法时间复杂度:O(n) 。需要计算数组中的第n个元素,每个元素的计算都可以在 O(1) 的时间内完成。为什么比最小堆法复杂度更低呢?

最小堆的方法是先存再排,三指针的方法则是先排再存。

这两种思路在排序时有重大区别。最小堆在排序时,是一堆数字在排序,因此是 O(logn) 的排序时间;三指针法在排序时,是3个数字在排序,因此是 O(1) 的排序时间。最终两种方法的总时间复杂度是 O(nlong) 和 O(n) 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值