題目
面試題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
解題思路
旋轉之後的數組可以劃分爲兩個子數組,第一個子數組中的元素都大於等於第二個子數組中的元素。
本題採用二分法,將mid元素與right元素比較,算法步驟如下:
1)如果mid元素小於right元素,說明mid右邊的數組是遞增數組,那麼最小值必然在mid左邊,right = mid;
2)如果mid元素大於right元素,說明最小值在mid與right之間,left = mid + 1;
3)如果相等,說明mid與right區間的元素相等重複,right–;
複雜度分析:
時間複雜度:平均時間複雜度爲 O(logN),其中 N 爲數組長度。但是在最壞情況下,也就是數組中包含相同元素時(nums[mid]==nums[right]),需要逐個遍歷元素,複雜度爲 O(N)。
空間複雜度:O(1)。
一些值得注意的問題:
1)爲什麼不用左邊位置 left 和中間位置 mid 的值進行比較?
舉例:[3, 4, 5, 1, 2] 與 [1, 2, 3, 4, 5] ,此時,中間位置的值都比左邊大,但最小值一個在後面,一個在前面,因此這種做法不能有效地解決問題。
2)爲什麼要用右邊位置 right 和中間位置 mid 的值進行比較?
舉例:[1, 2, 3, 4, 5]、[3, 4, 5, 1, 2]、[2, 3, 4, 5 ,1],用右邊位置和中間位置的元素比較,可以進一步縮小搜索的範圍。
3)當遇到 nums[mid] == nums[right] 的時候,不能確定最小元素在中軸元素的左邊還是右邊。這種情況下,爲了縮小查找範圍,安全的方法是將右邊界指針減一(right = right - 1),這樣可以有效地避免死循環,同時可以保證永遠不會跳過最小元素。
4)數組中元素重複會影響算法的時間複雜度嗎?
可以把問題 153.尋找旋轉排序數組中的最小值 看成這個問題的一個特例。這道題的所有解法也都適用於 153.尋找旋轉排序數組中的最小值 ,只是永遠不會進入 nums[mid] == nums[right] 的分支。也正是因爲可能包含重複數字,在 nums[mid] == nums[right] 分支下才會導致最差時間複雜度爲 O(N)。
代碼
class Solution {
public int minArray(int[] numbers) {
if(numbers.length == 0){
return 0;
}
int left = 0;
int right = numbers.length - 1;
while(left<right){
int mid = left + (right - left)/2;
if(numbers[mid] < numbers[right]){
// 後半部分遞增有序,最小值在前半部分,含mid
right = mid;
}else if(numbers[mid] > numbers[right]){
// 前半部分遞增有序,最小值在後半部分,不含mid
left = mid + 1;
}else{
// 如果相等,right--
right--;
}
}
return numbers[left];
}
}