【劍指Offer】旋轉數組的最小數字

題目描述

把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 NOTE:給出的所有元素都大於0,若數組大小爲0,請返回0。

解法1

對於非減數組來說,數組右邊的元素一定大於等於數組左邊的元素。當對非減數組進行旋轉後(把數組最開始的元素搬到末尾),則在遍歷過程中可能會出現右邊的元素反而小於左邊的元素,當第一次出現這種情況時,一定是原非減數組的開頭,即整個數組的最小元素。

實現代碼

public int minNumberInRotateArray(int[] rotateArray)
{
    if (rotateArray.Length <= 0)
    {
        return 0;
    }
    for (int i = 1; i < rotateArray.Length; i++)
    {
        if (rotateArray[i - 1] > rotateArray[i])
        {
            return rotateArray[i];
        }
    }
    return rotateArray[0];
}

二分查找

解法1是順序遍歷數組找到最小值,這樣的時間複雜度是O(n),那麼有沒有什麼辦法進行優化呢?
本題要查找的是非減排序數組的旋轉數組的最小值。其實是有一定順序的,對於有序數組的查找,我們自然想到了二分查找。
先介紹一下二分查找的基本思想:
首先,假設數組中元素是按升序排列,將數組中間位置元素與要查找的元素比較,如果兩者相等,則查找成功;否則利用中間位置索引將數組分成左、右兩個子數組,如果中間位置的元素大於要查找的元素,則進一步查找左子數組,否則進一步查找右子數組。重複以上過程,直到找到滿足條件的元素,使查找成功,或直到子數組不存在爲止,此時查找不成功。
二分查找的時間複雜度是O(log2n)

解法2

以數組arr = {3,4,5,1,2}爲例,可以分成兩個有序非減數組來看待,如下圖所示
兩個非減數組
顯然,數組的最小值就在兩個非減數組的交界處,同時由於arr是一個非減數組旋轉得到的,所以左邊數組的最小值一定大於等於右邊數組的最小值。利用二分查找,使左邊的指針指向索引0即3,右邊的索引指向索引4即2,求得mid = low + (high - low)/2 = 2,比較arr[mid]和arr[high]的值(這裏說明爲什麼不使用arr[mid]和arr[low]進行比較,因爲按照上面的算法,mid有可能等於low,再比較arr[mid]和arr[low]沒有意義)

  • 如果arr[mid] > arr[high],則說明當前的mid,處於左邊的非減數組中,則最小值在mid的右邊,則將low指向mid +
    1
  • 如果arr[mid] < arr[high],則說明當前的mid,處於右邊的非減數組中,則最小值在mid的左邊,則將high指向mid(這裏說明爲什麼high不指向mid - 1,同樣因爲mid的算法,可能存在mid = low = 0,如果high = mid - 1,則high有可能小於0,爲了避免這種情況的判斷,所以採用high = mid )

如果是求一個遞增數組的旋轉數組的最小值,則上述邏輯已經足夠,但本題是求非減數組的旋轉數組的最小值,也就是說可能存在兩個元素相等的情況。
比如旋轉數組{1,0,1,1,1}和{1,1,1,0,1}都可以看成非減數組{0,1,1,1,1}的旋轉數組
此時對於它們而言arr[mid] = arr[high],這種情況下我們並不知道mid是在最小值的左邊還是右邊,比如這兩個旋轉數組,一個是在mid的左邊,一個反而在mid的右邊。當出現這種情況時我們可以認爲數組的有序性丟失了,不能再繼續使用二分查找,而只能順序遍歷從low到high找到最小值,即high = high - 1或者low = low + 1。

實現代碼

public int minNumberInRotateArray(int[] rotateArray)
{
    if (rotateArray.Length <= 0) {
        return 0;
    }
    int low = 0, high = rotateArray.Length - 1;
    while(high > low) {
        int mid = low + (high - low) / 2;
        if (rotateArray[mid] > rotateArray[high]) {
            low = mid + 1;
        }else if (rotateArray[mid] < rotateArray[high]) {
            high = mid;
        }else {
            high = high - 1;
        }
    }
    return rotateArray[low];
}

一點想法

在想到用二分查找優化本題的時候,其實遇到了問題,就是上面有提到的當中間元素等於高位元素時,不知道應該左移還是右移的問題。一度覺得這道題可能用二分查找解不了,後來看到某個大神的代碼,才恍然大悟,這種情況下退化成順序查找就可以。想不到這種方法的原因還是太執着於二分查找的標準形式。一直是在套用算法,而沒有想到變通,或融合其他算法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章