经典算法题——寻找第1500个丑数

来源:互联网 发布:算法导论 第三版 pdf 编辑:程序博客网 时间:2024/05/21 03:16

前言:

        相信很多朋友们都对“丑数”有所了解,当然肯定也有人觉得这个名字好奇怪,咦,什么样的数才算是丑数咧?实际上丑数就是只包括2,3,5这三种因子的数,另外我们一般把“1”当作第一个丑数。

        知道了丑数,我们很容易发现,丑数有点像质数一样,总是没有最大的。不过丑数的寻找可比质数的寻找简单得多啦,那让我们来试一试找出从小到大排序的第1500个丑数吧。


解法一:

       既然知道了这个丑数的判断方法,也就是任意给我们一个自然数,我们可以判断它究竟是不是丑数。方法当然就是把这个数 a 先不断除以2,直到有余数,恢复一步,得到 x;再把 x 不断除以3,直到有余数,恢复一步,得到 y ;最后把 y 不断除以5,如果最后余数为 1 ,就说明这个数 a 是丑数,余数不为 1 则说明 a 包含其他除了2,3,5的因子。

        看一看代码:

int number = 1500; //要求输出丑数的数
int index = 1; //数字本身
while(number > 0){
    int temp = index;
    while(temp%2 == 0)temp = temp/2;
    while(temp%3 == 0)temp = temp/3;
    while(temp%5 == 0)temp = temp/5;
    if(temp == 1)
    { 
    System.out.println(index+"number="+number);
    number--;
    }
    index++;
}


        但是,但是,我们发现运行以上代码,电脑就开始不停地算了~~~~算啊算啊算啊算,差不多要几十秒才能得到我们想要的数。这是为什么呢?噢,看一下我们得到的那个第1500个丑数就明白了,859963392。。。好大。。。

        也就是说,我们的计算机从1开始一直判断所有的数是不是丑数,一直判断到我们这个 8 亿多这么大的这个数。。。原来人家计算机几十秒算了这么多东西在此,我想崇敬地说一句“计算机,您辛苦了(*^__^*) ”

        可是,这样计算机太可怜了,一定有什么方法帮帮她~~~~哎,对噢,判断需要判断8亿次,但是丑数只有1500个,如果我们能只做1500次操作而不是8亿次操作,效率自将大升。我们可以看看能不能直接计算出丑数,也就是说,给出现有的丑数,我们来找出下一个丑数,可以吗?当然啦~



解法二:

        赶紧来试一试。不过假设给了我们从1开始的10个丑数,怎么找出第11个呐?等等,诶,丑数不是只由2,3,5构成的嘛,而且我们拿到的数也是由2,3,5构成的,而且是最小的10个,那么第11个一定就比前十个数中的某一个多一个因子2,或者因子3,或者因子5……也就是说,第11个丑数一定是前十个丑数中某一个丑数的2倍,或者3倍,或者5倍!!

        这个很关键,再仔细想一想噢。现在知道了第11个丑数一定是前十个丑数中某一个丑数的2倍,或者3倍,或者5倍,还是不行呢,那到底是2倍还是3倍还是5倍呢o(︶︿︶)o。。。

        想起来啦,我们的第十一个丑数一定是,大于第十个丑数的所有丑数中,最小的!!!那就是说,我们可以在第一到第十个丑数中找出某个丑数 a ,它的两倍刚好大于第十个丑数(刚好的意思就是,上一个数的两倍还小于第十个丑数,而它自己的两倍却大于第十个丑数了);然后再找出前十个丑数中的三倍刚好大于第十个丑数的家伙 b ;最后找出前十丑数中某丑数5倍刚好大于第十个丑数的丑数 c ~~~2*a,3*b,5*c,找出这三个中最小的就行啦(*^__^*) 

        来来来,看看代码咯:

int num[]=new int[1501];
num[0] = 0;
num[1] = 1;
int two = 0,three = 0,five = 0;//刚好大于现有最大丑数的三个
for(int i = 1; i <= 1499; i++)

{//每次找出一个丑数
    for(int j = 1; j <= i ; j++)//从头向后扫描,若某数的两倍刚好大于上回找出的丑数,将它值记录下来
    if((2*num[j-1] <= num[i])&&(2*num[j] > num[i])) two=num[j]*2;
    for(int j = 1; j <= i ; j++)//从头向后扫描,若某数的三倍刚好大于上回找出的丑数,将它值记录下来
    if((3*num[j-1] <= num[i])&&(3*num[j] > num[i])) three=num[j]*3;
    for(int j = 1; j <= i ; j++)//从头向后扫描,若某数的五倍刚好大于上回找出的丑数,将它值记录下来
    if((5*num[j-1] <= num[i])&&(5*num[j] > num[i])) five=num[j]*5;
    //比较出三个数中最小的
    if(two>=three&&five>=three) num[i+1] = three;
    else if(two>=five&&three>=five) num[i+1] = five;
    else if(five>=two&&three>=two) num[i+1] = two;
}
System.out.println("第1500个丑数 = "+num[1500]);


        赶紧去跑一跑,有没有发现这速度比前一条快的不是一点半点哈哈,瞬间得到结果有木有



后话:

       我后来继续思索这个问题,隐隐约约感觉到还有更优的方法。因为丑数不是仅由2,3,5构成嘛,所以从理论上出发,完全可以自己用2,3,5自主构建出从小到大的丑数。我感觉比如拿到一个丑数,它的2,3,5的构成个数就一定了,那应该可以推断出下一个丑数的2,3,5的构成。如果能完成这个功能,其效率肯定比解法二更优。

        不过理论成立,实际不太可行,因为虽然知道某丑数2,3,5构成,下一个数的2,3,5构成确实应该是一定的,但是我们没办法通过简单的算法计算得到。

        所以实现此方法的唯一方式只能通过人为映射(就是相当于打表)。而这样的映射的复杂性增加速度是很恐怖的,所以这样的方法看来确实没有实现的可能了。

        当然这样的思路也提供了另一种方法,比如某丑数的2,3,5因子数分别为x,y,z ,那么下一个数的2,3,5因子数必将在0—(x+y*y+z*z*z),0—(x+y+z*z),0—(sqrt(x)+y+z)范围内,由此推断下一个。好吧,这个方法也麻烦的可以,不过我就想告诉大家,算法题的解法总是很多样化的,大家一定要亲自去探索,很可能获取一些意外收获噢(*^__^*) 


原创粉丝点击