作为一个小白,做剑指offer-33-丑数.想点首歌《braek up》

一、题目描述

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

二、开始手撕

第一次尝试:双重循环,运行时间爆炸

最开始的思路就是遍历所有数字,然后将他们分解因子,但是时间复杂度大:


public class Solution {
    public int GetUglyNumber_Solution(int index) {
      int n=0;
      int m=2;
        while(n<index)
        {
            if(cs(m))
              n++;
              m++;
        }
        return n;
    }
    public boolean cs(int num)
    {
        while(num%2==0)
            num=num/2;
        while(num%3==0)
            num=num/3;
        while(num%5==0)
            num=num/5;
        if(num==1)
            return true;
        return false;
    }
}

第二次尝试:在第一步的基础上改进,改成递归

但是治标不治本,时间复杂度仍然爆炸


public class Text {
	public static void main(String[] args)
    {
		
		    int n=1;
	        int sum=0;
	        int index=7;
	        int now=1;
	        while(sum<index)
	        {
	            if(cddg(n))
	                {
	            	sum++;
	                now=n;
	                }
	                
	            n++;
	        }
		System.out.println(now);

    }
	public static boolean cddg(int cs)
    {
		
		if((cs%2==0&&cs/2==1)||(cs%3==0&&cs/3==1)||(cs%5==0&&cs/5==1)||cs==1)
			{
			
			return true;
			}
		int two=cs/2;
		
		int three=cs/3;
		int five=cs/5;
        if(two*2==cs)
        	return cddg(two);
        else if(three*3==cs)
        	return cddg(three);
        else if(five*5==cs)
        	return cddg(five);
        else
        	return false;
    }
     
}

第三次尝试:从2的基础上得到灵感,一个丑数的所有因子也必然是丑数

于是我将2进行改进,只需要判断一个数除2(或除3或除5)得到的数是否在集合内则可以判断该数是否是丑数,我用了HashSet的contains方法比较。
最后的结果是,超时。

import java.util.HashSet;
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index<=0)
            return 0;
      HashSet set = new HashSet();
		    set.add(1);
		    set.add(2);
		    set.add(3);
		    set.add(5);
		    
		    int n=1;
	        int sum=1;
	       
	        int now=1;
	        while(sum<index)
	        {
	            
	        	if(n%2==0&&set.contains(n/2))
	        	{
	        		set.add(n);
	        		now=n;
	        		sum++;
	        		
	        	}
	        	else if(n%3==0&&set.contains(n/3))
	        	{
	        		set.add(n);
	        		now=n;
	        		sum++;
	        		
	        	}
	        		else if(n%5==0&&set.contains(n/5))
	        		{
		        		set.add(n);
		        		now=n;
		        		sum++;
		        		
		        	}
	        		
	            n++;
	        }
        return now;
    }
}

我(此时的)认为原因主要出现在HashSet的contains的方法上,是否是这个方法的实现使我的时间爆表,所以我要另辟道路。

第四次尝试:既然由3知道了每个丑数的所有因子也必然是丑数,那么每个丑数除2(或除3或除5)会是怎样的

1由可知上图,每一个丑数由它的前面的丑数与2,3,5相乘得到,而且最后的商仍然按照丑数增长规律。

在这里插入图片描述但明显有部分丑数是重复计算的,所以我们去除重复项后可见。
此处因表格限制未显示出来,事实上数据够长可以发现,当重复项删除后,无论是2或3或5,所产生的数值集都都按照该倍数以某个规律进行被增。
如3:1 3 5 9 15 27 45 是隔2个数进行3的倍增。当然我们这里不对此进行讨论

既然每个数集合是与原排列规律相同,那么我是否可以进行遍历,每找一个数,判断其除2或除3过除5后是否的数字是否在集合内。这个看起来思路好像和3差不多,但其实3的方法在这种具有明显排序递增,且有规律的集合中会显得过于浪费。
因此我使用了队列,利用队列先进先出的特性,当遇到相同时出列,入列。但是,运行时间仍然超时了。

import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        Queue<Integer> queue2 =new LinkedList();
         Queue<Integer> queue3 =new LinkedList();
         Queue<Integer> queue5=new LinkedList();
        queue2.add(1);
        queue3.add(1);
        queue5.add(1);
        int sum=1;
        int now=1;
       for(int n=2;sum<index;n++)
       {
           if(n%2==0&&n/2==queue2.peek())
           {
               queue2.add(n);
               queue2.poll();
               sum++;
               now=n;
           }
           else if(n%3==0&&n/3==queue3.peek())
           {
               queue2.add(n);
               queue3.add(n);
               queue3.poll();
               sum++;
               now=n;
           }
           else if(n%5==0&&n/5==queue5.peek())
           {
               queue2.add(n);
               queue3.add(n);
               queue5.add(n);
               queue5.poll();
               sum++;
               now=n;
           }
       }
        return now;
        
    }
}

此时的我十分苦恼,不懂得为什么只用了一个循环还会超时

第五次尝试:我将4的代码由队列存储改为数组,利用双箭头,一个指向数组目前存储位置,一个指向数组当前取值位置。

好吧,时间超时了,意料之内,因为其实并没有什么方法上的创新,只是存储方式改了,甚至损耗空间还更大了,也并不是没有什么收获,起码我知道了不是队列内部存储的方法(起码不是主要原因)导致时间超时,我的逻辑本身是有错误的。

import java.util.*;
import java.util.Queue;
import java.util.LinkedList;
public class Text {
	public static void main(String[] args)
    {
		long start = System.currentTimeMillis(); 
		Scanner sc =new Scanner(System.in);

		int[] i2 =new int[10000];
		int[] i3 =new int[10000];
		int[] i5 =new int[10000];
		int n2=0;
		int nn2=0;
		int n3=0;
		int nn3=0;
		int n5=0;
		int nn5=0;
	    i2[n2]=1;
	    i3[n3]=1;
	    i5[n5]=1;
	 
		   int index=sc.nextInt();
		   System.out.print(1+" ");
	        int sum=1;
	        
	        int now=1;
	        int a=0;
	         for(int n=2;sum<index;n++)
	        {
	        	
	            if(n%2==0&&n/2==i2[nn2])
	            {
	                
	               i2[++n2]=n;
	               nn2++;
	                sum++;
	                now=n;
	                System.out.print(n+" ");
	            }
	            else if(n%3==0&&n/3==i3[nn3])
	            {
	            	i2[++n2]=n;
	            	i3[++n3]=n;
	            	nn3++;
	                sum++;
	                now=n;
	                System.out.print(n+" ");
	            }
	            else if(n%5==0&&n/5==i5[nn5])
	            {
	            	i2[++n2]=n;
	            	i3[++n3]=n;
	            	i5[++n5]=n;
	            	nn5++;
	                sum++;
	                now=n;
	                System.out.print(n+" ");
	            }
	        }
	      System.out.println();
	      //此处写要测试的代码
	    	long end = System.currentTimeMillis(); 
	    	System.out.println("共耗时"+(end-start)+"毫秒");
          
    }
	
	
     
}

第六次尝试(success):我试着输出更多数据,发现丑数的分布数字越大,前后两个丑数相差就会越远,我总算发现一个致命的错误:遍历。当想要知道第1600个丑数后想知道第1601个时,中间有可能需要遍历几十上百万个数,这是时间的巨额浪费。

:我在5的基础上改进,既然不能遍历,那就从第一个开始往后推算,将计算后的结果写入数组,同样是双指针,大致与5相同。

import java.util.*;
import java.util.Queue;
import java.util.LinkedList;
public class Text {
	public static void main(String[] args)
    {
		long start = System.currentTimeMillis(); 
		Scanner sc =new Scanner(System.in);

		long[] i2 =new long[100000];
		long[] i3 =new long[100000];
		long[] i5 =new long[100000];
		int n2=0;
		int nn2=0;
		int n3=0;
		int nn3=0;
		int n5=0;
		int nn5=0;
	    i2[n2]=1;
	    i3[n3]=1;
	    i5[n5]=1;
	 
		   int index=sc.nextInt();
		   System.out.print(1+" ");
	        int sum=1;
	        
	        long now=1;
	        
	        while(sum<index)
	        {
	        	long a=i2[nn2]*2;
	        	long b=i3[nn3]*3;
	        	long c=i5[nn5]*5;
	        	if(Min(a,b,c)==2)
	            {
	                
	               i2[++n2]=a;
	               nn2++;
	                sum++;
	                now=a;
	                System.out.print(a+" ");
	            }
	            else if(Min(a,b,c)==3)
	            {
	            	i2[++n2]=b;
	            	i3[++n3]=b;
	            	nn3++;
	                sum++;
	                now=b;
	                System.out.print(b+" ");
	            }
	            else if(Min(a,b,c)==5)
	            {
	            	i2[++n2]=c;
	            	i3[++n3]=c;
	            	i5[++n5]=c;
	            	nn5++;
	                sum++;
	                now=c;
	                System.out.print(c+" ");
	            }
	        }
	        int innow=(int)now;
	      System.out.println(innow);
	      //此处写要测试的代码
	    	long end = System.currentTimeMillis(); 
	    	System.out.println("共耗时"+(end-start)+"毫秒");
          
    }
	public static int Min(long a,long b,long c) 
	{
		if(a<b&&a<c)
			return 2;
		if(b<c&&b<a)
			return 3;
		if(c<b&&c<a)
			return 5;
		return 0;
	}
	
     
}

终于
在这里插入图片描述

虽然排名很低,但确实很开心

看看牛人:既然排名低,又怎能不看看优秀答案呢,牛客牛人还是蛮多的。

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index <= 0)return 0;
        int p2=0,p3=0,p5=0;//初始化三个指向三个潜在成为最小丑数的位置
        int[] result = new int[index];
        result[0] = 1;//
        for(int i=1; i < index; i++){
            result[i] = Math.min(result[p2]*2, Math.min(result[p3]*3, result[p5]*5));
            if(result[i] == result[p2]*2)p2++;//为了防止重复需要三个if都能够走到
            if(result[i] == result[p3]*3)p3++;//为了防止重复需要三个if都能够走到
            if(result[i] == result[p5]*5)p5++;//为了防止重复需要三个if都能够走到
 
 
        }
        return result[index-1];
    }
}

在这里插入图片描述

以三个指针来记录当前乘以2、乘以3、乘以5的最小值,然后当其被选为新的最小值后,要把相应的指针+1;因为这个指针会逐渐遍历整个数组,因此最终数组中的每一个值都会被乘以2、乘以3、乘以5。


总结:乍看之下这位大牛的好像和自己的差不多(吹牛不犯法吧),但其实上他用单指针比我用双指针所耗费的时间自然节省不少。两者有本质上的不同,因为我根本没拐到动态规划那去。

啊啊啊,真的就差一拐了。

学习之路漫漫无期,任何学到的知识都是自己的武器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值