丑数
题目连接:(这俩都一样)
题目
———————————————————————————————————————————
给你一个整数 n ,请你找出并返回第 n 个 丑数 。
丑数 就是只包含质因数 2
、3
和/或 5
的正整数。
示例 1:
输入:n = 10
输出:12
解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。
示例 2:
输入:n = 1 输出:1 解释:1 通常被视为丑数。
提示:
-
1 <= n <= 1690
———————————————————————————————————————————
小根堆(用到java的优先队列)+哈希
关键点:
1:建堆:建立起优先队列,初始添加元素1
2:取数:每次取出队列的堆头,再把取出的数依次乘上2,3,5后查重并调用offer插入形成小根堆
3:查重:有了新的堆元素,需要判断重复的元素,就需要用到哈希hashSet
4:计数:统计取对头的次数
时间复杂度:O(nlogn)
空间复杂度:O(n)
代码:
class Solution {
public int nthUglyNumber(int n) {
//定义三个质因数2,3,5
int[] factors = {2, 3, 5};
//创建一个哈希表后面用于查重
// import java.util.HashSet;
//import java.util.Set;
Set<Long> seen = new HashSet<Long>();
//创建一个优先队列
//import java.util.PriorityQueue;
PriorityQueue<Long> heap = new PriorityQueue<Long>();
//把初始根1加入哈希表和小顶堆中(long型要写为1L)
//调用 offer()方法后,往堆中压入元素然后从下往上调整堆为小顶堆。
seen.add(1L);
heap.offer(1L);
int ugly;
for (int i = 0; i < n; i++)
{
//取出并删处根节点的丑数。
//poll 方法每次从 PriorityQueue 的头部删除一个节点
long curr = heap.poll();
ugly = (int) curr;
//取每个质因数乘以根节点,如果没有重复,插入小顶堆
for (int factor : factors) {
long next = curr * factor;
//如果没有重复,加入哈希表
if (seen.add(next)) {
//插入丑数,并调整为小顶堆
heap.offer(next);
}
}
}
return ugly;
}
}
注:
1.优先队列的逻辑结构是一棵完全二叉树,存储结构其实是一个数组。
逻辑结构层次遍历的结果刚好是一个数组
2.offer方法:往堆中压入元素然后从下往上调整堆为小顶堆。
3.poll方法:在堆中每次删除只能删除头节点,并将最后一个节点替代头节点然后进行调整。
优先队列其他方法详解链接:
其他方法:
1.暴力(超时)
int nthUglyNumber(int n){
//使用双层循环去表遍历对应的数是否为丑数
//直接超时
int count= 0;// 统计目标值
for(i =1; i< INT_MAX;i++) //枚举
{
int m = i;
while (n % 2 == 0) {m /= 2;}
while (n % 3 == 0) {m /= 3;}
while (n % 5 == 0) {m /= 5;}
if(m == 1) {count++; }
if(count == n){return m;}
}
}
2.动态规划(我觉得比小根堆简单)
原理:
题解链接 剑指 Offer 49. 丑数(动态规划,清晰图解)
代码:
class Solution {
public int nthUglyNumber(int n) {
int a = 0, b = 0, c = 0;
int[] dp = new int[n];
dp[0] = 1;
for(int i = 1; i < n; i++) {
int n2 = dp[a] * 2, n3 = dp[b] * 3, n5 = dp[c] * 5;
dp[i] = Math.min(Math.min(n2, n3), n5);
if(dp[i] == n2) a++;
if(dp[i] == n3) b++;
if(dp[i] == n5) c++;
}
return dp[n - 1];
}
}
进阶题目链接: