丑数

作者: 囧略囧 | 来源:发表于2020-02-17 10:47 被阅读0次

    题目描述

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

    这里说的因子其实指的是质因子,比如8含有因子4,但4不是质因子,8的质因子为2。

    解法一:

    最直观暴力的解题思路就是碰到一个数字,便去检测它是不是丑数,若是则记录下来,否则继续检测下一个数。
    代码如下:

    public class Solution {
        public static int GetUglyNumber_Solution(int index) {
            int n = 1;
            int i = 1;
            while(i != index) {
                n++;
                if(isUgly(n)) {
                    i++;
                }
            }
            return n;
             
        }
        public static boolean isUgly(int num) {
            while(num % 2 == 0)
                num /= 2;
            while(num % 3 == 0)
                num /= 3;
            while(num % 5 == 0)
                num /= 5;
            if(num == 1)
                return true;
            else
                return false;
        }
    }
    

    但是这种方法时间性能很差,如果两个丑数之间距离较大,会产生很多无意义计算,最终导致超时。

    解法二:

    由于丑数的因子只包含2、3、5,那么一个丑数m必定由一个更小的丑数n乘上因子(n * 2或n * 3或n * 5)得到,也就是说,通过递推可以得到之后的丑数,那么我们便可以只计算丑数,比解法一减少了很多工作量。

        起始的第一个丑数是1,通过1*2,1*3,1*5我们可以得到三个丑数,但是数组中的丑数需要升序排列,所以我们只能先在数组中放入最小的2, 得到[1,2]。
        由于第一个数乘以2已经被遍历过了,按照递推,丑数序列中乘以2后值最小的只能是第二个数(已乘以2的不计)。所以我们将指针s2置为1,指向第二个数。由于第一个数可以生成的三个丑数只放入了一个乘以2得到的,那么s3和s5依旧为0,指向第一个数。
        将2*2,1*3,1*5进行比较,取最小的1*3,得到[1,2,3],第一个数的乘以3也被遍历到了,令s3 = 1指向第二数。s2依旧为1,s5为0。
        ……
        我们每一次都是取最小的值放入,然后更新其指针。如果最小的值出现重复怎么办(如array[s1]*2 == array[s5]*5),这时应该向数组中放入该值后,s1和s5均更新,相当于这两个值均放进了数组,只不过是用一个位置,这样就可以保证数组中的数字无重复。
        看到这里我们可以总结发现,s2、s3和s5实质上就是用来记录每一个已经得到的丑数可推导出的三个丑数,通过s2、s3和s5的移动,数组中已有的每一个丑数的三个递推丑数均得到了遍历。通过取最小值和相同值合并实现数组的递推有序增长。
    
    public class Solution {
        public static int GetUglyNumber_Solution(int index) {
            if(index <= 0)
                return 0;
            int s2 = 0, s3 = 0, s5 = 0;
            int[] result = new int[index];
            result[0] = 1;
            for(int i = 1; i < index; i++) {
                result[i] = Math.min(result[s2] * 2, Math.min(result[s3] * 3, result[s5] * 5));
                if(result[i] == result[s2] * 2)
                    s2++;
                if(result[i] == result[s3] * 3)
                    s3++;
                if(result[i] == result[s5] * 5)
                    s5++;
            }
            return result[index - 1];
        }
        
    }
    
    解法三:

    这是在剑指 offer上的解法,实质计算过程与解法二相同,但是可以从另一个角度来理解。
    假设已有的最大丑数为M,记要生成的下一个丑数为N。则N一定是之前的某一个丑数乘以2、3或5的结果。
    遍历之前的丑数,将其均乘以2,由于数组是有序的,则一定存在一个结果首先大于M,记为X2;
    遍历之前的丑数,将其均乘以3,由于数组是有序的,则一定存在一个结果首先大于M,记为X3;
    遍历之前的丑数,将其均乘以5,由于数组是有序的,则一定存在一个结果首先大于M,记为X5;
    由于数组是有序的,新插入的数字一定是递推生成的丑数中最小的,则必在X2,X3,X5中。(因为X2是乘以2生成的大于M的最小值,X3,X5同理)
    取最小值后更新指针。

    public class Solution {
        public static int GetUglyNumber_Solution(int index) {
            if(index <= 0)
                return 0;
            int s2 = 0, s3 = 0, s5 = 0;
            int[] result = new int[index];
            result[0] = 1;
            for(int i = 1; i < index; i++) {
                result[i] = Math.min(result[s2] * 2, Math.min(result[s3] * 3, result[s5] * 5));
                while(result[s2] * 2 <= result[i])
                    s2++;
                while(result[s3] * 3 <= result[i])
                    s3++;
                while(result[s5] * 5 <= result[i])
                    s5++;
            }
            return result[index - 1];
        }
        
    }
    

    相关文章

      网友评论

          本文标题:丑数

          本文链接:https://www.haomeiwen.com/subject/ftkvqctx.html