劍指 Offer 11. 旋轉陣列的最小數字

iOS_Asia發表於2020-12-12

劍指 Offer 11. 旋轉陣列的最小數字

劍指 Offer 11. 旋轉陣列的最小數字
把一個陣列最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。輸入一個遞增排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。例如,陣列 [3,4,5,1,2] 為 [1,2,3,4,5] 的一個旋轉,該陣列的最小值為1。

示例 1:

輸入:[3,4,5,1,2]
輸出:1

示例 2:

輸入:[2,2,2,0,1]
輸出:0


思路1:遍歷

題目的意思:輸入一個遞增排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。
話說,管他是不是旋轉陣列,直接找出並返回陣列的最小值即可。
也就是:找出陣列的最小值
從前往後比較,遇到更小的替換,最後找出最小值
不難寫出程式碼:

class Solution {
    public int minArray(int[] numbers) {
        //邊界條件
        if(numbers == null || numbers.length == 0) return 0;
        
        int min = numbers[0];
        for(int i = 0; i<numbers.length; i++){
            if(numbers[i] < min)
            {
                min = numbers[i];
            }
        }
        return min;
    }
}

時間複雜度:O(n)
空間複雜度:O(1)


思路2:遍歷的優化

但是,這道題應該不是讓我們這樣解的,因為沒有使用到原陣列是排序好的陣列這個資訊。

假如,我們將輸入的旋轉後的陣列再次改變為排序好的陣列,那麼,直接找出陣列第一個元素就是最小值。
如何將輸入後的旋轉後的陣列再次改變為排序好的陣列呢?
可以從前往後找,找到一個前面的數字大於後一個數字的地方
可以從後往前找,找到一個前面的數字大於後一個數字的地方

其實,仔細想想,又沒有讓我們恢復排序陣列,那麼就沒有必要對陣列進行操作
那麼,我們只需要找到一個前面的數字大於後一個數字的地方即可

從前往後找:

class Solution {
    public int minArray(int[] numbers) {
        //邊界條件
        if(numbers == null || numbers.length == 0) return 0;
        
        //從前往後,找出第一個前面的數,大於後面的數的後面的數
        int min = numbers[0];
        for(int i = 0; i<numbers.length; i++){
            if(numbers[i] < min)
            {
                min = numbers[i];
                break;
            }
        }
        return min;
    }
}

比之前的程式,只是多了一個break


看了答案,解題是使用的二分查詢
這倒也對,排序的陣列,使用二分查詢效率要高
但,問題是,這個陣列也並不是排序的陣列,而是排序陣列的旋轉
這樣看來,這道題是考察二分查詢的變種,如何在旋轉後的排序陣列中,使用二分查詢找到最小值

思路3:二分查詢

假設:
最小值所在的位置為i,值nums[i] = x;
那麼,位置i之前的資料都小於x;位置i之後的資料都大於或等於x

為何前面是小於,後面是大於或等於呢?

比如[1,1,2,2,0,0]
那麼,最小值的位置index可以是4,也可以是5,因為兩個位置的值都是0
但,根據是排序的規則,我們可以預設第一個最小值為最小值,也就是index = 4的地方為最小值。
但是,最後求的是最小值,而不是位置,所以,index = 4或 index = 5都可以,反正最後返回的值都是0

其實,這裡,你也可以規定i之前的資料都是小於或等於x,i之後的資料都是大於x。

根據上面的特徵,可以得到nums[middle]的值,然後分情況討論:

  1. 如果nums[middle] > nums[height],說明最小值在middle的右邊

在這裡插入圖片描述

  1. 如果nums[middle] < nums[height],說明最小值在middle的左邊

在這裡插入圖片描述

  1. 如果nums[middle] == nums[height]
    在這裡插入圖片描述

那麼,最小值不能確定在左邊還是右邊,
不能減少為minArray(numbers, low, middle);,有可能把最小值在右邊的情況忽略掉
但是,可以使得height - 1,這是因為,height - 1最多是middle的地方,也沒有把最小值忽略掉

根據以上思路,嘗試寫程式碼:


class Solution {
    public int minArray(int[] numbers) {
        //邊界條件
        if(numbers == null || numbers.length == 0) return 0;
        
        return minArray(numbers, 0, numbers.length - 1);
    }

    //左閉右開
    public int minArray(int[] numbers, int low, int height)
    {
        // int middle = (height - low)/2;
        int middle = low + (height - low)/2;
                    // = low + height/2 - low/2
                    // = height/2 + low/2;

        if(low < height){
            if(numbers[middle] > numbers[height]){
                return minArray(numbers, middle + 1, height);
            }else if(numbers[middle] < numbers[height]){
                return minArray(numbers, low, middle);
            }else{
                return minArray(numbers, low, --height);
            }
        }else{
            return numbers[low];
        }

        
    }
}

需要注意的地方:
int middle = (height - low)/2;❌
int middle = low + (height - low)/2;✅
int middle = (height + low)/2;✅

–height,因為要用到height - 1的結果

為何:minArray(numbers, middle + 1, height);
為何:minArray(numbers, low, middle);
你們不覺得這很怪異嗎?
為何前面的middle可以+1操作,而後面那個middle不可以做-1操作?

為何minArray(numbers, middle + 1, height);
能不能+1,是看+1後,會不會把最小值省略掉
如第一張圖,nums[middle + 1] > nums[middle],所以不會省略最小值
即使,nums[middle + 1]正好就是最小值,nums[middle + 1] < nums[middle]
而求的是minArray(numbers, middle + 1, height);,依然不會省略掉最小值,所以,此處middle + 1是可以的

為何minArray(numbers, low, middle);
能不能middle - 1,是看-1後,會不會把最小值省略掉
如第二張圖所示,nums[middle - 1] < nums[middle],不會省略最小值
問題是,如果nums[middle]此時正好就是最小值,那麼,nums[middle - 1] > nums[middle],此時minArray(numbers, low, middle - 1);就會把最小值nums[middle]給漏掉,因此,middle-1是不可以的

相關文章