題目:把一個數組最開始的若干個元素搬到數組的末尾,咱們稱之爲數組的旋轉。輸入一個遞增排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。面試
這道題最直觀的解法並不難,從頭至尾遍歷數組一次,咱們就能找出最小的元素。這種思路的時間複雜度顯然是O(n)。可是這個思路沒有利用輸入的旋轉數組的特性,確定達不到面試官的要求。數組
咱們注意到旋轉以後的數組實際上能夠劃分爲兩個排序的子數組,並且前面的子數組的元素都大於或者等於後面子數組的元素。咱們還注意到最小的元素恰好是這兩個子數組的分界線。在排序的數組中咱們能夠用二分查找法實現O(logn)的查找。單元測試
Step1.和二分查找法同樣,咱們用兩個指針分別指向數組的第一個元素和最後一個元素。測試
Step2.接着咱們能夠找到數組中間的元素:spa
若是該中間元素位於前面的遞增子數組,那麼它應該大於或者等於第一個指針指向的元素。此時數組中最小的元素應該位於該中間元素的後面。咱們能夠把第一個指針指向該中間元素,這樣能夠縮小尋找的範圍。移動以後的第一個指針仍然位於前面的遞增子數組之中。若是中間元素位於後面的遞增子數組,那麼它應該小於或者等於第二個指針指向的元素。此時該數組中最小的元素應該位於該中間元素的前面。3d
Step3.接下來咱們再用更新以後的兩個指針,重複作新一輪的查找。指針
按照上述的思路,第一個指針老是指向前面遞增數組的元素,而第二個指針老是指向後面遞增數組的元素。最終第一個指針將指向前面子數組的最後一個元素,而第二個指針會指向後面子數組的第一個元素。也就是它們最終會指向兩個相鄰的元素,而第二個指針指向的恰好是最小的元素。這就是循環結束的條件。code
之前面的數組{3,4,5,1,2}爲例,下圖展現了在該數組中查找最小值的過程:blog
public static int GetMin(int[] numbers) { if (numbers == null || numbers.Length <= 0) { return int.MinValue; } int index1 = 0; int index2 = numbers.Length - 1; // 把indexMid初始化爲index1的緣由: // 一旦發現數組中第一個數字小於最後一個數字,代表該數組是排序的 // 就能夠直接返回第一個數字了 int indexMid = index1; while (numbers[index1] >= numbers[index2]) { // 若是index1和index2指向相鄰的兩個數, // 則index1指向第一個遞增子數組的最後一個數字, // index2指向第二個子數組的第一個數字,也就是數組中的最小數字 if (index2 - index1 == 1) { indexMid = index2; break; } indexMid = (index1 + index2) / 2; // 特殊狀況:若是下標爲index一、index2和indexMid指向的三個數字相等,則只能順序查找 if (numbers[index1] == numbers[indexMid] && numbers[indexMid] == numbers[index2]) { return GetMinInOrder(numbers, index1, index2); } // 縮小查找範圍 if (numbers[indexMid] >= numbers[index1]) { index1 = indexMid; } else if (numbers[indexMid] <= numbers[index2]) { index2 = indexMid; } } return numbers[indexMid]; } public static int GetMinInOrder(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; }
這裏須要注意的是:排序
(1)把indexMid初始化爲index1的緣由:一旦發現數組中第一個數字小於最後一個數字,代表該數組是排序的,就能夠直接返回第一個數字了。
(2)特殊狀況的分析:若是下標爲index一、index2和indexMid指向的三個數字相等,則只能順序查找,所以這裏定義了一個GetMinInOrder()方法。
(1)典型輸入,單調升序的數組的一個旋轉
// 典型輸入,單調升序的數組的一個旋轉 [TestMethod] public void GetMinNumTest1() { int[] array = {3, 4, 5, 1, 2}; Assert.AreEqual(Program.GetMin(array),1); }
(2)有重複數字,而且重複的數字恰好的最小的數字
// 有重複數字,而且重複的數字恰好的最小的數字 [TestMethod] public void GetMinNumTest2() { int[] array = { 3, 4, 5, 1, 1, 2 }; Assert.AreEqual(Program.GetMin(array), 1); }
(3)有重複數字,但重複的數字不是第一個數字和最後一個數字
// 有重複數字,但重複的數字不是第一個數字和最後一個數字 [TestMethod] public void GetMinNumTest3() { int[] array = { 3, 4, 5, 1, 2, 2 }; Assert.AreEqual(Program.GetMin(array), 1); }
(4)有重複的數字,而且重複的數字恰好是第一個數字和最後一個數字
// 有重複的數字,而且重複的數字恰好是第一個數字和最後一個數字 [TestMethod] public void GetMinNumTest4() { int[] array = { 1, 0, 1, 1, 1 }; Assert.AreEqual(Program.GetMin(array), 0); }
(5)單調升序數組,旋轉0個元素,也就是單調升序數組自己
// 單調升序數組,旋轉0個元素,也就是單調升序數組自己 [TestMethod] public void GetMinNumTest5() { int[] array = { 1, 2, 3, 4, 5 }; Assert.AreEqual(Program.GetMin(array), 1); }
(6)數組中只有一個數字
// 數組中只有一個數字 [TestMethod] public void GetMinNumTest6() { int[] array = { 2 }; Assert.AreEqual(Program.GetMin(array), 2); }
(7)魯棒性測試:輸入NULL
// 魯棒性測試:輸入NULL [TestMethod] public void GetMinNumTest7() { Assert.AreEqual(Program.GetMin(null), int.MinValue); }
單元測試的結果以下圖所示:
對於GetMin方法編寫的單元測試的代碼覆蓋率已達到了100%: