12. 旋轉數組的最小數字
問題描述
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。輸入一個遞增排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。
很容易我們想到的是,從頭到尾遍歷一次數據,我們就找出了最小值,時間複雜度是O(n),但是我們一點都沒有用到旋轉數組的特性.
分析
- 旋轉數組,其實可以劃分爲兩個排序的子數組,而且前面的子數組的元素都大於後面子數組的元素
- 最小值剛好就是這兩個子數組的分界線
- 我們可以嘗試用二分查找來實現(時間複雜度 O(logn))
代碼如下:
/**
* Class day12 ...
*
* @author LiJun
* Created on 2018/12/28
*/
public class day12 {
public static int Min(int[] numbers) {
// 參數有效性驗證
if (numbers == null || numbers.length <= 0) {
return Integer.MIN_VALUE;
}
// 二分法的基本實現就是一個指向頭 一個指向尾
int start = 0;
int end = numbers.length - 1;
// 設置medium的原因是一旦數組中的第一個數字 小於最後一個數字,說明這個數組是排序的
// 直接返回第一個數字就行
int medium = start;
while (numbers[start] >= numbers[end]) {
// 如果start 和 end 執行相鄰的數字
// 則 start 執行第一個遞增數列的最後一個數字
// end 指向第二個遞增數列的第一個數字
if (end - start == 1) {
medium = end;
break;
}
medium = (start + end) / 2;
// 這裏是特殊情況 如果這三者相等的話,就只能老老實實的順序查找了
if(numbers[start] == numbers[medium] && numbers[medium] == numbers[end]){
return GetMinInOrder(numbers, start, end);
}
if(numbers[medium] >= numbers[end]){
start = medium;
}else if(numbers[medium] <= numbers[end]){
end = medium;
}
}
return numbers[medium];
}
public static int GetMinInOrder(int[] numbers, int start, int end) {
int min = numbers[start];
for(int i = start+1; i <= end; i++){
if(numbers[i] < min){
min = numbers[i];
}
}
return min;
}
public static void main(String[] args) {
// 基礎測試
int[] numbers = {3,4,5,1,2};
System.out.println(Min(numbers));
numbers = new int[]{1, 0, 1, 1, 1};
System.out.println(Min(numbers));
}
}
在這我需要解釋下,如果numbers[start] < numbers[end]
這就說明,這區間已經是排序好了的,無須查找了。
其次,有種情況,那就是 根據旋轉數組的特性,第一個數字總是大於或者等於最後一個數字,那麼我們現在想想,如果我們把 0 個數據搬到後面去 這時候第一個數據就是最小的,這也就是我們爲什麼把 medium
初始爲 start
的值的原因。
但是我們也有種情沒考慮到,當 numbers[start] = numbers[end] =numbers[medium]
的時候, 這個時候,我們應該怎麼辦呢。 例如: {1, 0, 1, 1, 1} 之類的,我們可以得到 numbers[medium] = 1
這三個值都相等的時候,我們怎樣二分。我們只能老老實實的順序遍歷找出答案了。