劍指Offer #06 旋轉數組的最小數字(二分查找)| 圖文詳解

題目來源:牛客網-劍指Offer專題
題目地址:旋轉數組的最小數字

題目描述

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

題目解析

方法一:
最直觀的方法莫過於漠視題目給的條件“非遞減排序”,直接遍歷整個數組找到裏面最小的元素。就算我很菜,也不忍下手寫這樣的代碼。

於是,對暴力法稍加優化纔有了第一種算法:以第一個元素 array[0]array[0] 爲基準找到第一個比它小的元素,這個就是原數組的第一個元素,即最小值。找不到則返回第一個元素,說明沒有元素參與旋轉。1
雖然做了優化,在極端情況下還是要遍歷整個數組,時間複雜度 O(n)O(n)

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
    	//特判
        if (array.length == 0) {
            return 0;
        }
        //以第一個元素爲基準
        int flag = array[0];
        for (int i = 1; i < array.length; i++) {
        	//找到第一個比它小的元素,即爲答案
            if (flag > array[i]) {
                return array[i];
            }
        }
        return flag;
    }
}

方法二:
通常在有序的序列中查找某個值的問題都可以用二分查找,本題雖然不是整個序列又是有序的,但是它也算是局部有序,也可以用上二分查找。

經過觀察我們可以發現,旋轉數組可以分成左右兩個部分,而我們要找的數就在分界線的右邊。算法描述如下:

  • 首先,我們設定左右指針 leftleftrightright ,然後通過判斷中間指針 midmid 指向的數是否比 rightright 指向的數小。
  • 如果是,則說明 midmid 位於數組右邊部分,於是將 rightright 指針移動到該位置上;否則,說明 midmid 位於數組左邊部分,於是將指針移動到該位置上。
  • 最後不斷重複上訴過程,直到找到數組兩個部分的邊界所在,即 rightleft=1right-left=1。這時,array[right]array[right] 就是我們要找的最小值。
    2

二分查找的過程如上圖所示 ,算法時間複雜度爲 O(logn)O(logn)

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if (array.length == 0) {
            return 0;
        }
        int left = 0, right = array.length - 1;
        while (array[left] >= array[right]) {
            int mid = (left + right) / 2;
            //找到邊界所在
            if (right - left == 1) break;
            if (array[mid] < array[right]) {
                right= mid;
            } else {
                left= mid;
            }
        }
        return array[right];
    }
}

雖然這樣在牛客網中通過了,但是我發現了一組可以hack這份代碼的數據:
[3,4,1,3,3,3,3][3,4,1,3,3,3,3]
正確答案: 11
代碼返回: 33

方法三:
有問題就肯定會有對策,這裏借用牛客網大佬FINACK的思路。

分下面三種情況進行討論:

  1. array[mid] > array[right]時,
    出現這種情況的array類似[3,4,5,6,0,1,2],此時最小數字一定在mid的右邊。
    \therefore left = mid + 1
  2. array[mid] == array[right]時,
    出現這種情況的array類似 [1,0,1,1,1] 或者[1,1,1,0,1],此時最小數字不好判斷在mid左邊還是右邊,這時只好逐個嘗試。
    \therefore right = right - 1
  3. array[mid] < array[right]:
    出現這種情況的array類似[2,2,3,4,5,6,6],此時最小數字一定就是array[mid]或者在mid的左邊。因爲右邊必然都是遞增的。
    \therefore right = mid

注意這裏有個坑:如果待查詢的範圍最後只剩兩個數,那麼mid 一定會指向下標靠前的數字,比如 array = [4,6],此時array[left] = 4 ;array[mid] = 4;array[right] = 6;如果right = mid - 1,就會產生錯誤, 因此right = mid。

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if (array.length == 0) {
            return 0;
        }
        int left = 0, right = array.length - 1;
        while (left < right) {
            int mid = (left + right) / 2;
            if (array[mid] > array[right]) {
                left = mid + 1;
            } else if (array[mid] == array[right]) {
                right--;
            } else {
                right = mid;
            }
        }
        return array[left];
    }
}

只是可惜,在所有數字都一樣的情況下,算法的時間複雜度會退化成 O(n)O(n)。當然,這是沒有辦法的事情。


如果本文對你有所幫助,要記得點贊哦~

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