每日一題 - 11. 旋轉陣列的最小數字
題目資訊
-
時間: 2019-07-23
-
題目連結:Leetcode
-
tag: 二分查詢
-
難易程度:簡單
-
題目描述:
把一個數組最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。輸入一個遞增排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。例如,陣列 [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
解題思路
本題難點
排序陣列的旋轉,找到旋轉點,效能最佳。
具體思路
尋找旋轉陣列的最小元素即為尋找 右排序陣列 的首個元素 numbers[x]
排序陣列的查詢問題首先考慮使用 二分法 解決,其可將遍歷法的 線性級別 時間複雜度降低至 對數級別 。
- 迴圈二分:
- 當 numbers[m] > numbers[j]時: m 一定在 左排序陣列 中,即旋轉點 x 一定在 [m+1,j] 閉區間內,因此執行 i=m+1;
- 當 numbers[m] < numbers[j] 時:m 一定在 右排序陣列 中,即旋轉點 x 一定在[i,m] 閉區間內,因此執行 j=m;
- 當 numbers[m] == numbers[j] 時: 無法判斷 m 在哪個排序陣列中,即無法判斷旋轉點 x 在 [i,m] 還是 [m+1,j] 區間中。解決方案: 執行 j=j−1 縮小判斷範圍 。
展開分析 numbers[m] == numbers[j]
情況:
-
無法判定 m 在左(右)排序陣列: 設以下兩個旋轉點值為 0 的示例陣列,則當 i=0, j=4 時 m=2 ,兩示例結果不同。
-
例 [1,0,1,1,1] :旋轉點 x=1 ,因此 m=2 在 右排序陣列 中。
-
例 [1,1,1,0,1] :旋轉點 x=3 ,因此 m=2 在 左排序陣列 中。
-
-
j=j−1 操作的正確性證明:只需證明每次執行此操作後,旋轉點 x 仍在 [i,j] 區間內即可。
-
若 m 在右排序陣列中: numbers[m] == numbers[j] ,因此陣列 [m,j](恆有 m<j)區間內所有元素值相等,執行 j=j−1 只會拋棄一個重複值,因此旋轉點 x 仍在 [i,j] 區間內。
-
若 m 在左排序陣列中: 由於 左排序陣列 任一元素 >= 右排序陣列 任一元素 ,因此可推出旋轉點元素值 numbers[x] <= numbers[j] == numbers[m],則有:
-
若 numbers[x] < numbers[j] : 即 j 左方仍有值更小的元素,執行 j=j−1 後旋轉點 x 仍在 [i,j] 區間內。
-
若
numbers[x] == numbers[j]
: 分為以下兩種情況。當 j>xj>x : 易得執行 j=j−1j=j−1 後旋轉點 xx 仍在 [i,j] 區間內。
當 j=x: 特殊情況,即執行 j=j−1 後旋轉點 x 可能不在 [i,j] 區間內。例如 [1,1,1,2,3,1] ,當 i=0 , m=2 , j=5 時執行 j=j−1 後雖然 丟失了旋轉點索引 x=5 ,但最終返回值仍正確(最終返回的 numbers[0] 等於旋轉點值 numbers[5] ),這是因為:之後的二分迴圈一直在執行 j=m ,而區間 [i,m] 內的元素值一定都等於旋轉點值 numbers[x] ( ∵ 區間內元素值既要滿足 ≥ 也要滿足 ≤ numbers[x]) ,因此 仍可保證正確的返回值 。
-
-
提示 是否可以用
numbers[m]
和numbers[i]
比較做代替?不可以。因為做比較的目的是判斷 m 在哪個排序陣列中。但在 numbers[m] > numbers[i]情況下,無法判斷 m 在哪個排序陣列中。本質是因為 j 初始值肯定在右排序陣列中; i 初始值無法確定在哪個排序陣列中。
程式碼
class Solution {
public int minArray(int[] numbers) {
int l = 0;
int r = numbers.length-1;
while(l < r){
int mid = (l + r)/2;
if(numbers[mid] > numbers[r]){
l = mid + 1;
}else if(numbers[mid] < numbers[r]){
r = mid;
}else{
r--;
}
}
return numbers[l];
}
}
複雜度分析:
- 時間複雜度 O(logN) : 在特例情況下(例如 [1,1,1,1]),會退化到 O(N)。
- 空間複雜度 O(1) : i , j , m指標使用常數大小的額外空間。