Leetcode Week4 Find Minimum in Rotated Sorted Array II
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