劍指Offer面試題:29.丑數


一、題目:丑數

題目:我們把只包含因子2、3和5的數稱作丑數(Ugly Number)。求按從小到大的順序的第1500個丑數。例如6、8都是丑數,但14不是,因為它包含因子7。習慣上我們把1當做第一個丑數。

二、兩種解決方案

2.1 一一遍歷法:時間效率低下

  使用遍歷法求第k個丑數,從1開始遍歷,如果是丑數則count++,直到count=k為止。那么如何判斷丑數呢?根據丑數的定義,丑數只有2,3,5這三個因子,那么我們就拿數字除以這三個因子。具體算法如下:

Step1.如果一個數能夠被2整除,那么讓他繼續除以2;

Step2.如果一個數能夠被3整除,那么讓他繼續除以3;

Step3.如果一個數能夠被5整除,那么讓他繼續除以5;

Step4.如果最后這個數變為1,那么這個數就是丑數,否則不是。

  根據以上算法實現代碼如下:

    public int GetUglyNumber(int index)
    {
        if (index <= 0)
        {
            return 0;
        }

        int number = 0;
        int uglyCount = 0;

        while (uglyCount < index)
        {
            number++;

            if (IsUgly(number))
            {
                uglyCount++;
            }
        }

        return number;
    }

    private bool IsUgly(int number)
    {
        while (number % 2 == 0)
        {
            number /= 2;
        }

        while (number % 3 == 0)
        {
            number /= 3;
        }

        while (number % 5 == 0)
        {
            number /= 5;
        }

        return number == 1 ? true : false;
    }

  該算法非常直觀,代碼也非常簡潔,但最大的問題就在於每個整數都需要計算。即使一個數字不是丑數,我們還是需要對它做求余數和除法操作。因此該算法的時間效率不是很高,

2.2 空間換時間法:時間效率較高

  根據丑數的定義,我們可以知道丑數可以由另外一個丑數乘以2,3或者5得到。因此我們可以創建一個數組,里面的數字是排好序的丑數,每一個丑數都是前面的丑數乘以2,3或者5得到的。

  我們把得到的第一個丑數乘以2以后得到的大於M的結果記為M2。同樣,我們把已有的每一個丑數乘以3和5,能得到第一個大於M的結果M3和M5那么M后面的那一個丑數應該是M2,M3和M5當中的最小值:Min(M2,M3,M5)。比如將丑數數組中的數字按從小到大乘以2,直到得到第一個大於M的數為止,那么應該是2*2=4<M,3*2=6>M,所以M2=6。同理,M3=6,M5=10。所以下一個丑數應該是6。

  根據以上思路實現代碼如下:

    public int GetUglyNumber(int index)
    {
        if (index <= 0)
        {
            return 0;
        }

        int[] uglyNumbers = new int[index];
        uglyNumbers[0] = 1;
        int nextUglyIndex = 1;

        int multiply2 = 0;
        int multiply3 = 0;
        int multiply5 = 0;
        int min = 0;

        while (nextUglyIndex < index)
        {
            min = Min(uglyNumbers[multiply2] * 2, uglyNumbers[multiply3] * 3, uglyNumbers[multiply5] * 5);
            uglyNumbers[nextUglyIndex] = min;

            while (uglyNumbers[multiply2] * 2 <= uglyNumbers[nextUglyIndex])
            {
                multiply2++;
            }

            while (uglyNumbers[multiply3] * 3 <= uglyNumbers[nextUglyIndex])
            {
                multiply3++;
            }

            while (uglyNumbers[multiply5] * 5 <= uglyNumbers[nextUglyIndex])
            {
                multiply5++;
            }

            nextUglyIndex++;
        }

        int result = uglyNumbers[index - 1];
        uglyNumbers = null;

        return result;
    }

    private int Min(int num1, int num2, int num3)
    {
        int min = num1 < num2 ? num1 : num2;
        min = min < num3 ? min : num3;

        return min;
    }

  和第一種方案相比,第二種方案不需要在非丑數的整數上做任何計算,因此時間效率有明顯提升。但也需要指出,第二種算法由於需要保存已經生成的丑數,因此需要一個數組,從而增加了空間消耗。如果是求第1500個丑數,將創建一個能容納1500個丑數的數組,這個數組占內存6KB。

三、單元測試與性能對比

3.1 單元測試

  (1)測試用例

    public static void Main(string[] args)
    {
        Program p = new Program();
        p.Test(1);
        p.Test(2);
        p.Test(3);
        p.Test(4);
        p.Test(5);
        p.Test(6);
        p.Test(7);
        p.Test(8);
        p.Test(9);
        p.Test(10);
        p.Test(11);
        p.Test(1500);
        p.Test(0);
        p.Test(1500);

        Console.ReadKey();
    }

    public void Test(int index)
    {
        int result = GetUglyNumberWithSpace(index);
        Console.WriteLine("Test result is {0}", result);
        Console.WriteLine("-------------End-------------");
    }
View Code

  (2)測試結果

3.2 性能對比

  這里我們使用兩種解決方案來求第1500個丑數,通過下面的圖片可以清楚地看到兩種方案的響應時間。(這里借助老趙的CodeTimer類來進行時間效率的監測)

  (1)一一遍歷法:65秒,等得花兒都謝了

  

  (2)空間換時間法:4毫秒,迅雷不及掩耳

  

  由對比可以看出,一個簡單的優化,再通過6KB的空間換取了巨大的時間效率,在實際開發中是一個值得實踐的解決思路(當然,事先得權衡一下利弊)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM