问题
编写一个程序,找出第 n 个丑数。
丑数就是只包含质因数 2, 3, 5 的正整数。
1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
1 是丑数。
n 不超过1690。
解题思路
- 暴力解法 ,创建一个动态数组存储已经找到的丑数,数字从1开始每次加1,判断是否能被2,3,5整除,除数是否在数组中,满足则将其加入数组,直到数组大小为n,结果在n接近1300时超时;
- 小顶堆解法,创建一个小顶堆存储已经找到的丑数,这里为了复习堆的操作,我用的动态数组,且手撸一遍小顶堆堆的插入和删除操作,每次将堆顶元素乘以2,3,5,并将其插入小顶堆,然后删除堆顶,时间复杂度为O(nlogn),空间复杂度为O(n);
小顶堆解法代码
class Solution {
// 维护小顶堆,先加后删
List<Long> heapArray;
int count;
public int nthUglyNumber(int n) {
if ( n == 0 ) {
return 0;
}
heapArray = new ArrayList<>();
heapArray.add(1L);
count = 0;
Long root = 1L;
while(count != n-1) {
root = upholdHeap(root);
count++;
}
return root.intValue();
}
public Long upholdHeap(Long num) {
Long num1 = num * 2;
Long num2 = num * 3;
Long num3 = num * 5;
// 往小顶堆增加节点
if (!heapArray.contains(num1)) {
heapArray.add(num1);
upshif(heapArray.size() - 1);
}
if (!heapArray.contains(num2)) {
heapArray.add(num2);
upshif(heapArray.size() - 1);
}
if (!heapArray.contains(num3)) {
heapArray.add(num3);
upshif(heapArray.size() - 1);
}
// 删除堆顶,将堆最后一个元素放到堆顶,且从上往下调整
heapArray.set(0, heapArray.get(heapArray.size() - 1));
heapArray.remove(heapArray.size() - 1);
downshif(0);
return heapArray.get(0);
}
// 从下往上调整顺序
public void upshif(int child) {
if (child < 1) {
return;
}
// 满足左子节点且小于父节点,与父亲交换数据
if (child%2 != 0 && heapArray.get(child) < heapArray.get(child/2) ) {
int parent = child/2;
Long temp = heapArray.get(child) ;
heapArray.set(child, heapArray.get(parent));
heapArray.set(parent, temp);
upshif(parent);
}
// 满足右子节点且小于父节点,与父亲交换数据
if (child%2 == 0 && heapArray.get(child) < heapArray.get(child/2 - 1)) {
int parent = child/2 - 1;
Long temp = heapArray.get(child) ;
heapArray.set(child, heapArray.get(parent));
heapArray.set(parent, temp);
upshif(parent);
}
}
// 从上往下调整,最后无法调整则删除此节点
public void downshif(int root) {
int lchild = root * 2 + 1;
int rchild = root * 2 + 2;
int minchild = 0;
// 找到最小子节点
if (lchild > heapArray.size() - 1) {
return;
} else if (rchild > heapArray.size() - 1){
minchild = lchild;
} else {
if (heapArray.get(lchild) < heapArray.get(rchild)) {
minchild = lchild;
} else {
minchild = rchild;
}
}
// 若比父亲小则交换
if (heapArray.get(minchild) < heapArray.get(root)) {
Long temp = heapArray.get(minchild);
heapArray.set(minchild, heapArray.get(root));
heapArray.set(root, temp);
downshif(minchild);
}
}
}
总结
这里还有需要优化的地方,比如已经找到了n个节点,则可以返回最大的那个节点,但是并不知道是否是第n个待插入节点即是最大,小顶堆中的所有叶子节点需要遍历一遍查找最大节点才是要求的那个数;