劍指Offer面試題:7.旋轉數組的最小數字


一、題目:旋轉數組的最小數字

題目:把一個數組最開始的若干個元素搬到數組的末尾,我們稱之為數組的旋轉。輸入一個遞增排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。

  這道題最直觀的解法並不難,從頭到尾遍歷數組一次,我們就能找出最小的元素。這種思路的時間復雜度顯然是O(n)。但是這個思路沒有利用輸入的旋轉數組的特性,肯定達不到面試官的要求

  我們注意到旋轉之后的數組實際上可以划分為兩個排序的子數組,而且前面的子數組的元素都大於或者等於后面子數組的元素。我們還注意到最小的元素剛好是這兩個子數組的分界線。在排序的數組中我們可以用二分查找法實現O(logn)的查找

二、解題思路

  Step1.和二分查找法一樣,我們用兩個指針分別指向數組的第一個元素和最后一個元素。

  Step2.接着我們可以找到數組中間的元素:

  如果該中間元素位於前面的遞增子數組,那么它應該大於或者等於第一個指針指向的元素。此時數組中最小的元素應該位於該中間元素的后面。我們可以把第一個指針指向該中間元素,這樣可以縮小尋找的范圍。移動之后的第一個指針仍然位於前面的遞增子數組之中。如果中間元素位於后面的遞增子數組,那么它應該小於或者等於第二個指針指向的元素。此時該數組中最小的元素應該位於該中間元素的前面。

  Step3.接下來我們再用更新之后的兩個指針,重復做新一輪的查找。

按照上述的思路,第一個指針總是指向前面遞增數組的元素,而第二個指針總是指向后面遞增數組的元素。最終第一個指針將指向前面子數組的最后一個元素,而第二個指針會指向后面子數組的第一個元素。也就是它們最終會指向兩個相鄰的元素,而第二個指針指向的剛好是最小的元素。這就是循環結束的條件。

  以前面的數組{3,4,5,1,2}為例,下圖展示了在該數組中查找最小值的過程:

三、解決問題

3.1 代碼實現

    public static int GetMin(int[] numbers)
    {
        if (numbers == null || numbers.Length <= 0)
        {
            return int.MinValue;
        }

        int index1 = 0;
        int index2 = numbers.Length - 1;
        // 把indexMid初始化為index1的原因:
        // 一旦發現數組中第一個數字小於最后一個數字,表明該數組是排序的
        // 就可以直接返回第一個數字了
        int indexMid = index1;

        while (numbers[index1] >= numbers[index2])
        {
            // 如果index1和index2指向相鄰的兩個數,
            // 則index1指向第一個遞增子數組的最后一個數字,
            // index2指向第二個子數組的第一個數字,也就是數組中的最小數字
            if (index2 - index1 == 1)
            {
                indexMid = index2;
                break;
            }
            indexMid = (index1 + index2) / 2;
            // 特殊情況:如果下標為index1、index2和indexMid指向的三個數字相等,則只能順序查找
            if (numbers[index1] == numbers[indexMid] && numbers[indexMid] == numbers[index2])
            {
                return GetMinInOrder(numbers, index1, index2);
            }
            // 縮小查找范圍
            if (numbers[indexMid] >= numbers[index1])
            {
                index1 = indexMid;
            }
            else if (numbers[indexMid] <= numbers[index2])
            {
                index2 = indexMid;
            }
        }

        return numbers[indexMid];
    }

    public static int GetMinInOrder(int[] numbers, int index1, int index2)
    {
        int result = numbers[index1];
        for (int i = index1 + 1; i <= index2; ++i)
        {
            if (result > numbers[i])
            {
                result = numbers[i];
            }
        }

        return result;
    }

  這里需要注意的是:

  (1)把indexMid初始化為index1的原因:一旦發現數組中第一個數字小於最后一個數字,表明該數組是排序的,就可以直接返回第一個數字了。

  (2)特殊情況的分析:如果下標為index1、index2和indexMid指向的三個數字相等,則只能順序查找,因此這里定義了一個GetMinInOrder()方法。

3.2 單元測試

  (1)典型輸入,單調升序的數組的一個旋轉

    // 典型輸入,單調升序的數組的一個旋轉
    [TestMethod]
    public void GetMinNumTest1()
    {
        int[] array = {3, 4, 5, 1, 2};
        Assert.AreEqual(Program.GetMin(array),1);
    }

  (2)有重復數字,並且重復的數字剛好的最小的數字

    // 有重復數字,並且重復的數字剛好的最小的數字
    [TestMethod]
    public void GetMinNumTest2()
    {
        int[] array = { 3, 4, 5, 1, 1, 2 };
        Assert.AreEqual(Program.GetMin(array), 1);
    }

  (3)有重復數字,但重復的數字不是第一個數字和最后一個數字

    // 有重復數字,但重復的數字不是第一個數字和最后一個數字
    [TestMethod]
    public void GetMinNumTest3()
    {
        int[] array = { 3, 4, 5, 1, 2, 2 };
        Assert.AreEqual(Program.GetMin(array), 1);
    }

  (4)有重復的數字,並且重復的數字剛好是第一個數字和最后一個數字

    // 有重復的數字,並且重復的數字剛好是第一個數字和最后一個數字
    [TestMethod]
    public void GetMinNumTest4()
    {
        int[] array = { 1, 0, 1, 1, 1 };
        Assert.AreEqual(Program.GetMin(array), 0);
    }

  (5)單調升序數組,旋轉0個元素,也就是單調升序數組本身

    // 單調升序數組,旋轉0個元素,也就是單調升序數組本身
    [TestMethod]
    public void GetMinNumTest5()
    {
        int[] array = { 1, 2, 3, 4, 5 };
        Assert.AreEqual(Program.GetMin(array), 1);
    }

  (6)數組中只有一個數字

    // 數組中只有一個數字
    [TestMethod]
    public void GetMinNumTest6()
    {
        int[] array = { 2 };
        Assert.AreEqual(Program.GetMin(array), 2);
    }

  (7)魯棒性測試:輸入NULL

    // 魯棒性測試:輸入NULL
    [TestMethod]
    public void GetMinNumTest7()
    {
        Assert.AreEqual(Program.GetMin(null), int.MinValue);
    }

  單元測試的結果如下圖所示:

  對於GetMin方法編寫的單元測試的代碼覆蓋率已達到了100%:

 


免責聲明!

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



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