LeetCode-264. 丑数 II

题目来源

264. 丑数 II

题目详情

给你一个整数 n ,请你找出并返回第 n丑数

丑数 就是只包含质因数 23 和/或 5 的正整数。

示例 1:

输入: n = 10
输出: 12
解释: [1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。

示例 2:

输入: n = 1
输出: 1
解释: 1 通常被视为丑数。

提示:

  • 1 <= n <= 1690

题解分析

解法一:优先队列+Set

  1. 本题其实是一道比较简单的题目,但是需要考虑到题目中所描述的“丑数”的特点,那就是所有的丑数都是2,3,5的倍数,不存在其他可能。
  2. 为了找到指定的第n个丑数,我们需要从小到大枚举出每个丑数。但是,应该怎么枚举才可以保证顺序的从小打到呢?这个时候,我们可以想到,优先队列中就已经天然地维护了元素的顺序,所以我们可以借助优先队列来存储丑数。
  3. 此外,需要注意的一点是,单纯依靠优先队列是不够的,我们还需要判重,因为一个数有可能同时是2,3,5的倍数,所以这里会发生重复。
  4. 最后,还需要注意的一小点是,我们需要使用Long来存储元素,因为在枚举的过程中,n太大会导致中间元素可能超出int的最大值。
class Solution {
    public int nthUglyNumber(int n) {
        PriorityQueue<Long> que = new PriorityQueue<>(){{
            offer(1L);
        }};
        HashSet<Long> set = new HashSet<>();
        long now = 1;
        set.add(now);
        for(int i=1; i<=n; i++){
            now = que.poll();
            if(!set.contains(now * 2)){
                que.offer(now * 2);
                set.add(now * 2);
            }
                
            if(!set.contains(now * 3)){
                que.offer(now * 3);
                set.add(now * 3);
            }
                
            if(!set.contains(now * 5)){
                que.offer(now * 5);
                set.add(now * 5);
            }
                
        }
        return (int)now;
    }
}

解法二:动态规划

这道题一开始死活不明白三指针到底是怎么用的。后来突然就想明白了,我尝试解释一下:

例如 n = 10, primes = [2, 3, 5]。 打印出丑数列表:1, 2, 3, 4, 5, 6, 8, 9, 10, 12
首先一定要知道,后面的丑数一定由前面的丑数乘以2,或者乘以3,或者乘以5得来。例如,8,9,10,12一定是1, 2, 3, 4, 5, 6乘以2,3,5三个质数中的某一个得到。

这样的话我们的解题思路就是:从第一个丑数开始,一个个数丑数,并确保数出来的丑数是递增的,直到数到第n个丑数,得到答案。那么问题就是如何递增地数丑数?

观察上面的例子,假如我们用1, 2, 3, 4, 5, 6去形成后面的丑数,我们可以将1, 2, 3, 4, 5, 6分别乘以2, 3, 5,这样得到一共6*3=18个新丑数。也就是说1, 2, 3, 4, 5, 6中的每一个丑数都有一次机会与2相乘,一次机会与3相乘,一次机会与5相乘(一共有18次机会形成18个新丑数),来得到更大的一个丑数。

这样就可以用三个指针,

pointer2, 指向1, 2, 3, 4, 5, 6中,还没使用乘2机会的丑数的位置。该指针的前一位已经使用完了乘以2的机会。
pointer3, 指向1, 2, 3, 4, 5, 6中,还没使用乘3机会的丑数的位置。该指针的前一位已经使用完了乘以3的机会。
pointer5, 指向1, 2, 3, 4, 5, 6中,还没使用乘5机会的丑数的位置。该指针的前一位已经使用完了乘以5的机会。
下一次寻找丑数时,则对这三个位置分别尝试使用一次乘2机会,乘3机会,乘5机会,看看哪个最小,最小的那个就是下一个丑数。最后,得到下一个丑数的指针位置加一,因为它对应的那次乘法使用完了。

这里需要注意下去重的问题,如果某次寻找丑数,找到了下一个丑数10,则pointer2和pointer5都需要加一,因为5乘2等于10, 2乘5也等于10,这样可以确保10只被数一次。

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

参考

丑数 II-官方题解

posted @ 2022-03-30 15:17  Garrett_Wale  阅读(59)  评论(0编辑  收藏  举报