题目描述
牛客网: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] = 1
,x、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)