leetcode
leetcode 251 ~ 300
超级丑数

超级丑数

难度:

标签:

题目描述

超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes 中。

给你一个整数 n 和一个整数数组 primes ,返回第 n超级丑数

题目数据保证第 n超级丑数32-bit 带符号整数范围内。

 

示例 1:

输入:n = 12, primes = [2,7,13,19]
输出:32 
解释:给定长度为 4 的质数数组 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。

示例 2:

输入:n = 1, primes = [2,3,5]
输出:1
解释:1 不含质因数,因此它的所有质因数都在质数数组 primes = [2,3,5] 中。
 

提示:

  • 1 <= n <= 105
  • 1 <= primes.length <= 100
  • 2 <= primes[i] <= 1000
  • 题目数据 保证 primes[i] 是一个质数
  • primes 中的所有值都 互不相同 ,且按 递增顺序 排列

代码结果

运行时间: 140 ms, 内存: 21.9 MB


/* 
 思路: 
 使用最小堆来生成超级丑数。我们将初始丑数1放入堆中,然后不断从堆中取出最小的元素, 
 用给定的质数数组中的质数进行乘积,再将结果放入堆中,确保没有重复的元素。重复这一过程,直到得到第n个超级丑数。
 
 使用Java Stream实现代码。
 */
import java.util.*;
import java.util.stream.*;
 
public class SuperUglyNumberStream {
    public int nthSuperUglyNumber(int n, int[] primes) {
        PriorityQueue<Long> heap = new PriorityQueue<>();
        Set<Long> seen = new HashSet<>();
        heap.add(1L);
        seen.add(1L);
        long ugly = 1L;
        for (int i = 0; i < n; i++) {
            ugly = heap.poll();
            for (int prime : primes) {
                long next = ugly * prime;
                if (seen.add(next)) {
                    heap.add(next);
                }
            }
        }
        return (int) ugly;
    }
 
    public static void main(String[] args) {
        SuperUglyNumberStream solution = new SuperUglyNumberStream();
        int n = 12;
        int[] primes = {2, 7, 13, 19};
        System.out.println(solution.nthSuperUglyNumber(n, primes)); // 输出32
    }
}

解释

方法:

这个题解使用了最小堆来解决超级丑数问题。具体思路如下: 1. 初始化一个结果数组 ans,并将 1 作为第一个超级丑数加入 ans。 2. 使用一个最小堆 pq 存储 (prime, idx) 元组,其中 prime 表示质数,idx 表示该质数在 primes 数组中的下标。最小堆按照 prime 值进行排序。 3. 初始化一个数组 primes_idx,用于记录每个质数在 ans 数组中的下标。 4. 循环 n-1 次,每次从最小堆中取出最小的 (prime, idx) 元组,将 prime 加入 ans 数组。 5. 更新 primes_idx[idx],找到下一个未被使用的 ans[primes_idx[idx]],将其与当前质数相乘,得到新的超级丑数,并将其加入最小堆中。 6. 最终返回 ans 数组的最后一个元素,即第 n 个超级丑数。

时间复杂度:

O(n * log k),其中 n 是要求的超级丑数的编号,k 是质数数组的长度。

空间复杂度:

O(n + k),其中 n 是要求的超级丑数的编号,k 是质数数组的长度。

代码细节讲解

🦆
为什么选择使用最小堆来解这道超级丑数的问题?
最小堆结构能够有效地从多个质数的乘积结果中持续选出最小的数,保证生成的超级丑数是按顺序排列的。在每次从堆中取出最小元素后,都会生成新的可能的超级丑数并加入堆中,确保下一次仍然可以获取到最小值。使用最小堆可以避免对所有生成的丑数进行排序,从而提高效率。
🦆
在算法中,如果在最小堆中存在重复的超级丑数,是否会影响最终结果或效率?
如果最小堆中存在重复的超级丑数,它可能会稍微影响效率,因为同一个丑数会被多次处理和添加到结果数组中,尽管最终结果数组中不会包含重复的丑数。在实际实现中,可以通过设置或检查条件来避免将重复的丑数加入到最小堆中,从而优化算法的效率。
🦆
算法中提到的`更新 primes_idx[idx]`步骤中,为什么要寻找下一个未被使用的ans[primes_idx[idx]],这样做的目的是什么?
这一步骤的目的是为了确保每个质数都能乘以所有可能的先前生成的超级丑数,从而生成所有可能的新超级丑数。通过更新`primes_idx[idx]`,我们确保每个质数都与它之前的所有超级丑数相乘,这样可以保持超级丑数数组的完整性和正确性。
🦆
代码实现中提到将每个质数与ans数组中的对应元素相乘,这里的对应元素是如何选择的?
对应元素是通过质数的索引`idx`在`primes_idx`数组中的值来确定的。每次从堆中取出一个元素时,都会使用对应质数与ans数组中`primes_idx[idx]`位置的元素相乘,生成新的超级丑数。这确保了每个质数都依次与所有生成的超级丑数相乘,从而按正确的顺序生成每一个新的超级丑数。

相关问题

丑数 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