1. 程式人生 > >Leetcode Week4 Find Minimum in Rotated Sorted Array II

Leetcode Week4 Find Minimum in Rotated Sorted Array II

height play 提交 復雜 arr etc mic 兩個 範圍

Question

Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.

(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

Find the minimum element.

Answer

借用以下網上的翻譯:

  把一個數組最開始的若幹個元素搬到數組的末尾,我們稱之為數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。 NOTE:給出的所有元素都大於0,若數組大小為0,請返回0。

  下面我們來解題:

  最初可以通過畫圖理解,我想或許我們可以畫著以下的圖(為了方便,離散點畫成連續的):

技術分享圖片

  當然這個草圖也真是草圖,很粗糙,哈哈,而且不能解釋所有情況,它只能解決嚴格遞增。不過,這應該能解決大部分情況,至於其它小部分的情況我們等下再考慮。

主要思路

  我們頭腦第一個蹦出來的想法或許就是遍歷一次取最小值就好,這很簡單,也很容易實現,但這顯然不是我們想要的,時間復雜度為O(n)。

  我們想另外一種方法。非減?有序?有沒有覺得有點像二分查找,只不過它被分成兩截了,不過這並不能難倒我們,我們不妨試著用這思路解決。

  相對於二分查找,我們沒有所要查找的目標,數組也不是完全有序。不過,我們想想如何模仿這種過程,二分查找是取中間值來跟目標值比較進而縮小範圍的,而我們這題如何縮小範圍呢,取什麽比較呢,如果取中間值,又和誰比較呢。靠直覺,我們取最初點(設為A)、最後一個點(設為C),與中間點(設為B)比較,因為這三個點最具代表性,對於A與C來說,很顯然A>=C,我們再分析B,B可能在左半邊直線上,也有可能在右半邊直線;如果在左半邊直線,這時候B>=A>=C,這時候可以斷定最小值在B-C之間,因為B-C不是連續遞增的;如果在右半邊直線,這時候A>=C>=B,可以斷定最小值在A-B之間,因為A-B之間不是連續遞增的,肯定在某個地方有個“坑”。總結一下,如果B>C,就繼續在B-C查找,如果A>B,則在A-C查找。

技術分享圖片

  我們還需要個終止條件,就是讓這個查找終止下來。我們通過上面的算法不斷縮小搜索範圍,最終肯定縮小到兩個元素,因為如果存在三個元素以上,中間值是取除邊界兩個點(A,C)外的其中一個點,這樣下次搜索範圍能繼續縮小,直到兩個點。最後我們需要得到最小值,而兩個元素必然存在著最小值,但哪個是最小值呢,為了避免麻煩,我們可以比較一次得出最小值。

 (括號可以不看,單純說下我最初考慮兩個值種取最小值的過程,A與C之間的範圍逐漸縮小,逐漸逼近最小值,最後剩下的兩個點的分布有兩種可能,一種是分別在一條直線上,另一種可能是都在右半條直線上,然後,我們細想這個逼近的過程是可以排除第二種可能的,因為點A是不會隨著搜索範圍的減少而到達下面那條直線的。因此,只會分別在一條直線上,從圖上來看兩個點恰好是一上一下,這樣我們取下面那點,就是那個數組下標偏大的那個點即可,不過這會在我們沒有討論過的一些特例裏失效,比如旋轉數組為[1,2],因為這不符合我們上面討論的模型,因為[1,2]全部旋轉了,回到最初的位置,元素位置並沒有變化,因此我們采用比較兩個元素的方法得到最小值比較好)

  所以我們能像二分查找那樣寫出大致以下的代碼:

    int findMinVal(int min, int max, const vector<int> &rotateArray) {
        // 取三個點的值
        int minVal = rotateArray[min];             //最左邊的點的值
        int maxVal = rotateArray[max];             //最右邊的點的值
        int midVal = rotateArray[(min + max) / 2]; //中間的點的值

        // 終止條件
      if ((max - min) <= 1)
        return minVal > maxVal ? maxVal : minVal;

        // 根據三個點的值縮小搜索範圍
        if (midVal > maxVal)
            return findMinVal((min + max) / 2, max, rotateArray);
        else if (minVal > midVal)
            return findMinVal(min, (min + max) / 2, rotateArray);

        // 先無視這個return先,等等再討論
        return minVal;
    }

考慮特殊情況

  如果我們放上Leetcode提交,這肯定是過不了的。如果你是個謹慎的人,一定會想到有各種特殊情況,特別是三個點的值之間存在相等關系的時候。我們下面就來考慮這些情況。

  首先是如果midVal > maxVal或者minVal > midVal,那麽在所有情況下都是能正確的縮小搜索範圍的,這就不討論了。
  所以我們剩下要討論的情況是midVal <= maxVal 且 minVal <= midVal,組合起來就是minVal <= midVal <= maxVal。我們分四種情況考慮 。

  ①minVal < midVal < maxVal,這種情況存在於像[1,2,3]完全旋轉後和最初數組一樣的數組。這種很明顯取minVal即可。

②minVal < midVal = maxVal,這種和①一樣,例如數組為[1,2,2],取minVal即可。

③minVal = midVal < maxVal, 同①,例如[1,1,2],取minVal即可。

④minVal = midVal = maxVal, 這種情況有些特殊,不能縮小一半的範圍,也不能得出最小值,例如[1,1,0,1]或[1,1,0,1,1,1,1],所以只能一步一步縮小,即將索引min+1,max-1。

  ①-③可以合並。最終代碼如下:

  

    int findMin(vector<int>& nums) {
         if (nums.empty())
            return 0;
        return findMinVal(0, nums.size() - 1, nums);
    }

    int findMinVal(int min, int max, const vector<int> &rotateArray) {
 
        int minVal = rotateArray[min];
        int maxVal = rotateArray[max];
        int midVal = rotateArray[(min + max) / 2];
        if ((max - min) <= 1)
            return minVal > maxVal ? maxVal : minVal;
        if (midVal > maxVal)
            return findMinVal((min + max) / 2, max, rotateArray);
        else if (minVal > midVal)
            return findMinVal(min, (min + max) / 2, rotateArray);
        else if (minVal == midVal && midVal == maxVal)
            return findMinVal(min+1, max-1, rotateArray);
        return minVal;
    }

  由於上面的討論缺乏嚴格完整的數學證明過程,我不敢保證能考慮到所有情況,如果有漏了某些情況,請告訴我= =,哈哈。不過是能通過leetcode和牛客網的檢測的。

Leetcode Week4 Find Minimum in Rotated Sorted Array II