面試題11:旋轉陣列的最小數字
一、題目
把一個數組最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。輸入一個遞增排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。例如陣列{3, 4, 5, 1, 2}為{1, 2, 3, 4, 5}的一個旋轉,該陣列的最小值為1。
二、解法
分析:這道題最直觀的解法很簡單那,從頭到尾遍歷陣列一次,就得到了最小的元素,這種思路的時間複雜度顯然是O(n),但這種思路沒有利用輸入的旋轉陣列的特性,達不到面試官的要求。
我們可以利用二分查詢的思路解決這個問題,用兩個指標分別指向陣列的第一個元素和最後一個元素。按照題目中旋轉的規則,第一個元素應該是大於或者等於最後一個元素的。接著我們可以找到陣列中間的元素,如果中間元素大於等於第一個指標指向的元素,此時陣列中最小的元素應該位於中間元素的後面,可以把第一個指標指向該中間元素,這樣可以縮小尋找的範圍,如果中間元素小於等於第二個指標指向的元素,此時吧第二個指標指向中間元素。不管是移動第一個指標還是第二個指標,查詢範圍都會縮小到原來的一半,接下來我們再用更新之後的 兩個指標重複做新一輪的查詢。
基於上訴思路我們有以下程式碼
int Min(int *numbers, int length) { if(numbers==nullptr||length<=0) throw new std:: exception("Invalid parameters!"); int index1 = 0; int index2 = length-1; int indexMid = index1; while(numbers[index1] >= numbers[index2]) { if(index2-index1==1) { indexMid = index2; break; } indexMid = (index1+index2)/2; if(numbers[indexMid] >= numbers[index1]) index1 = indexMid; else if(numbers[indexMid] <= numbers[index2]) index2 = indexMid; } return numbers[indexMid]; }
針對前面的程式碼,其實存在一種bug,即若原始陣列為{0, 1, 1, 1, 1},其兩種旋轉陣列可以為{1, 0, 1, 1, 1}和{1, 1, 1, 0, 1},在前面的程式碼中,index1和index2對應的兩個數相同,並且,它們中間的數字indexMid指向的數也為1,即三個指標指向的數字都是相同的數字,在這種情況下,上面程式碼會出錯。對於第一種旋轉陣列:因為numbers[indexMid] >= numbers[index1],即index1=indexMid, 錯過了前面的數字0,認為最小數字在後面。
因此需要對前面的程式碼改進,當numbers[index1] == numbers[index2] && numbers[indexMid] == numbers[index1]時,採用順序查詢的方法:
//順序查詢
int MinInOrder(int* numbers, int index1, int index2)
{
int result = numbers[index1];
for(int i = index1 + 1; i <= index2; ++i)
{
if(result > numbers[i])
result = numbers[i];
}
return result;
}
int Min(int* numbers, int length)
{
if(numbers == nullptr || length <= 0)
throw new std::exception("Invalid parameters");
int index1 = 0;
int index2 = length - 1;
int indexMid = index1;
while(numbers[index1] >= numbers[index2])
{
// 如果index1和index2指向相鄰的兩個數,
// 則index1指向第一個遞增子陣列的最後一個數字,
// index2指向第二個子陣列的第一個數字,也就是陣列中的最小數字
if(index2 - index1 == 1)
{
indexMid = index2;
break;
}
// 如果下標為index1、index2和indexMid指向的三個數字相等,
// 則只能順序查詢
indexMid = (index1 + index2) / 2;
if(numbers[index1] == numbers[index2] && numbers[indexMid] == numbers[index1])
return MinInOrder(numbers, index1, index2);
// 縮小查詢範圍
if(numbers[indexMid] >= numbers[index1])
index1 = indexMid;
else if(numbers[indexMid] <= numbers[index2])
index2 = indexMid;
}
return numbers[indexMid];
}