好久不来这里写文章了呢!前几天本来是要做Super Ugly Number 这道题的,因为想接着之前做的题目复习一下数据结构中堆的知识。就在heap分类中找了一道medium,通过率看着挺高的题目,很久不来LeetCode写题目了,肯定是要挑一道较简单得练练手,练练脑~不过,以我自己的实际经历来说,不能以为AC率高的题就是简单的题。被虐了,然后看到它的姊妹题目,ugly number I和ugly number II。心想,先把Super ugly number这道题的前序题目做出来,那这道题肯定也不在话下了。现实又一次把我虐哭了,最后把ugly number I 和II都AC了,也没弄明白super ugly number...最后的最后,晚上跟小狐狸讨论了一下。第二天他说这道题对我来说有点难,就先放过它吧。。。
先看Ugly Number I,这道题目还是很简单的。判断一个数是否是丑陋数,也就是判断一个正数的质因数是否只包含2,3,5。在这道题中,1也是丑陋数。
分析:也就是说,如果一个数循环除以2,3,5之后余数是0,那么这个数就是丑陋数;如果余数不为0,说明这个数的质因数不只包含2,3,5还有其他数,也就不是丑陋数。有了思路后,代码实现就so easy了。
public boolean isUgly(int num) {
if(num<=0)
<span style="white-space:pre"> </span>return false;
if(num%2==0){
while(num%2==0){
num/=2;
}
}
if(num%3==0){
while(num%3==0){
num/=3;
}
}
if(num%5==0){
while(num%5==0){
num/=5;
}
}
if(num!=1)
return false;
return true;
}
把这道简单的题AC了,是不是也给自己增加了些许信心呢?接下来,让我们再来挑战自己吧~
ugly number II 这道题让我们找到第n个丑陋数,而不是只判断一个数是否是丑陋数辣么简单了。分析:一个大的丑陋数必然是一个比它小的丑陋数乘以2,3,5得到的。1是丑陋数,那么第二个丑陋数一定是第一个丑陋数分别乘以2,3,5后最小的一个,那么第二个丑陋数是2;同理,第三个丑陋数是2*2,1*3 和1*5中最小的一个,依次类推。可以把这个过程想象为三条不断延伸的数字链,每次都是有最小丑陋数的那一条或者几条链向下延伸(每一次比较数字链上可能会出现相同的最小丑陋数)。不知道这个过程你是不是想象出来了,把我画的一个简易图贴出来吧(手画的,跟博客大神们的图没法比)
从图中可以看到,每次都是持有最小丑陋数的数字链向下更新。
思想分析完了,剩下的就是如何实现它了。我最初的想法是,开辟三个队列,每个队列就是一个不断变化的数字链,取三个队列头元素进行比较,每次都是持有最小丑陋数的队列移出头元素,把新生成的最小丑陋数分别加入三个队列尾。
public int nthUglyNumber(int n) {
if(n==1)
return 1;
Queue<Integer> q1,q2,q3;
q1=new LinkedList<Integer>();
q2=new LinkedList<Integer>();
q3=new LinkedList<Integer>();
q1.add(1);
q2.add(1);
q3.add(1);
int nth_number=0;
int min=-1;
for(int i=1;i<n;i++){
int l1=q1.peek()*2;
int l2=q2.peek()*3;
int l3=q3.peek()*5;
// System.out.println(l1+","+l2+","+l3);
if(l1<=l2){
if(l1<=l3){
min=l1;
q1.remove();
if(l1==l2)
q2.remove();
if(l1==l3)
q3.remove();
}
else if(l3<l1){
min=l3;
q3.remove();
}
}
else if(l2<=l1){
if(l2<=l3){
min=l2;
q2.remove();
if(l2==l1)
q1.remove();
if(l2==l3)
q3.remove();
}
else if(l3<l2){
min=l3;
q3.remove();
}
}
// System.out.println("min is "+min);
q1.add(min);
q2.add(min);
q3.add(min);
// System.out.println(q1);
// System.out.println(q2);
// System.out.println(q3);
}
nth_number=min;
return nth_number;
}
最初的方法虽然也可以实现我们的算法思想,但是它开辟了三个队列,使得算法的空间复杂度较高。我使用队列其实只是想要实现三条数字链的延伸操作,那么可以在最初想法之前改进方法吗?当然是可以的啦!
改进一:仔细观察上图可以发现,其实这三条链可以用一条链代替,移出队列头的这种操作可以用指针的移动代替。因此,只需要一个数组来存储丑陋数,并设立三个指针分别指示三条数字链的当前位置。
public int nthUglyNumber(int n) {//改进一算法
int result[]=new int[n];
int p1=0,p2=0,p3=0;
result[0]=1;
for(int i=1;i<n;i++){
int min=Math.min(result[p1]*2, Math.min(result[p2]*3, result[p3]*5));
result[i]=min;
if(min==result[p1]*2)
p1++;
if(min==result[p2]*3)
p2++;
if(min==result[p3]*5)
p3++;
}
return result[n-1];
}
改进二:在这道题的discuss讨论区看到有小伙伴使用了PriorQueue,这种方法也是可以的哦,因为优先队列其实就是一个优先堆,队头就相当于堆顶,默认放着最小元素。有了这个数据结构我们就不用费劲吧啦的维护丑陋数的顺序了。向队列插入元素的过程就是一个调整堆的过程,所以堆顶永远都是我们想要的当前最小丑陋数。这里就不给出具体代码实现了,有兴趣的小伙伴可以尝试一下哦!