題目來源:牛客網-劍指Offer專題
題目地址:旋轉數組的最小數字
題目描述
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。
輸入一個非遞減排序的數組的一個旋轉,輸出旋轉數組的最小元素。
例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。
NOTE:給出的所有元素都大於0,若數組大小爲0,請返回0。
題目解析
方法一:
最直觀的方法莫過於漠視題目給的條件“非遞減排序”,直接遍歷整個數組找到裏面最小的元素。就算我很菜,也不忍下手寫這樣的代碼。
於是,對暴力法稍加優化纔有了第一種算法:以第一個元素 爲基準找到第一個比它小的元素,這個就是原數組的第一個元素,即最小值。找不到則返回第一個元素,說明沒有元素參與旋轉。
雖然做了優化,在極端情況下還是要遍歷整個數組,時間複雜度 。
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;
}
}
方法二:
通常在有序的序列中查找某個值的問題都可以用二分查找,本題雖然不是整個序列又是有序的,但是它也算是局部有序,也可以用上二分查找。
經過觀察我們可以發現,旋轉數組可以分成左右兩個部分,而我們要找的數就在分界線的右邊。算法描述如下:
- 首先,我們設定左右指針 和 ,然後通過判斷中間指針 指向的數是否比 指向的數小。
- 如果是,則說明 位於數組右邊部分,於是將 指針移動到該位置上;否則,說明 位於數組左邊部分,於是將指針移動到該位置上。
- 最後不斷重複上訴過程,直到找到數組兩個部分的邊界所在,即 。這時, 就是我們要找的最小值。
二分查找的過程如上圖所示 ,算法時間複雜度爲 。
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這份代碼的數據:
正確答案:
代碼返回:
方法三:
有問題就肯定會有對策,這裏借用牛客網大佬FINACK的思路。
分下面三種情況進行討論:
- array[mid] > array[right]時,
出現這種情況的array類似[3,4,5,6,0,1,2],此時最小數字一定在mid的右邊。
left = mid + 1 - array[mid] == array[right]時,
出現這種情況的array類似 [1,0,1,1,1] 或者[1,1,1,0,1],此時最小數字不好判斷在mid左邊還是右邊,這時只好逐個嘗試。
right = right - 1 - array[mid] < array[right]:
出現這種情況的array類似[2,2,3,4,5,6,6],此時最小數字一定就是array[mid]或者在mid的左邊。因爲右邊必然都是遞增的。
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];
}
}
只是可惜,在所有數字都一樣的情況下,算法的時間複雜度會退化成 。當然,這是沒有辦法的事情。
如果本文對你有所幫助,要記得點贊哦~