1. 程式人生 > >面試題11:旋轉陣列的最小數字

面試題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];
}