一、題目:數字在排序數組中出現的次數
題目:統計一個數字在排序數組中出現的次數。例如輸入排序數組{1,2,3,3,3,3,4,5}和數字3,由於3在這個數組中出現了4次,因此輸出4。
二、解題思路
2.1 直接運用二分查找
既然輸入的數組是排序的,那么我們很自然地就能想到用二分查找算法。在題目給出的例子中,我們可以先用二分查找算法找到一個3。由於3可能出現多次,因此我們找到的3的左右兩邊可能都有3,於是我們在找到的3的左右兩邊順序掃描,分別找出第一個3和最后一個3。因為要查找的數字在長度為n的數組中有可能出現O(n)次,所以順序掃描的時間復雜度是O(n)。因此這種算法的效率和直接從頭到尾順序掃描整個數組統計3出現的次數的方法是一樣的。
2.2 改進運用二分查找
接下來我們思考如何更好地利用二分查找算法。假設我們是統計數字k在排序數組中出現的次數。在前面的算法中時間主要消耗在如何確定重復出現的數字的第一個k和最后一個k的位置上,有沒有可能用二分查找算法直接找到第一個k及最后一個k呢?
我們先分析如何用二分查找算法在數組中找到第一個k。二分查找算法總是先拿數組中間的數字和k作比較。如果中間的數字比k大,那么k只有可能出現在數組的前半段,下一輪我們只在數組的前半段查找就可以了。如果中間的數字比k小,那么k只有可能出現在數組的后半段,下一輪我們只在數組的后半段查找就可以了。如果中間的數字和k相等呢?我們先判斷這個數字是不是第一個k。如果位於中間數字的前面一個數字不是k,此時中間的數字剛好就是第一個k。如果中間數字的前面一個數字也是k,也就是說第一個k肯定在數組的前半段,下一輪我們仍然需要在數組的前半段查找。
(1)GetFirstK:找到數組中第一個k的下標。如果數組中不存在k,返回-1
private static int GetFirstK(int[] data, int k, int start, int end) { if (start > end) { return -1; } int middIndex = (start + end) / 2; int middData = data[middIndex]; if (middData == k) { if ((middIndex > 0 && data[middIndex - 1] != k) || middIndex == 0) { return middIndex; } else { end = middIndex - 1; } } else if (middData > k) { end = middIndex - 1; } else { start = middIndex + 1; } return GetFirstK(data, k, start, end); }
(2)GetLastK:找到數組中最后一個k的下標。如果數組中不存在k,返回-1
private static int GetLastK(int[] data, int k, int start, int end) { if (start > end) { return -1; } int middIndex = (start + end) / 2; int middData = data[middIndex]; if (middData == k) { if ((middIndex < data.Length - 1 && data[middIndex + 1] != k) || middIndex == end) { return middIndex; } else { start = middIndex + 1; } } else if (middData > k) { end = middIndex - 1; } else { start = middIndex + 1; } return GetLastK(data, k, start, end); }
(3)GetNumberOfK:找到數組中第一個和最后一個k的下標進行減法運算得到最終結果
public static int GetNumberOfK(int[] data, int k) { int number = 0; if (data != null && data.Length > 0) { int first = GetFirstK(data, k, 0, data.Length - 1); int last = GetLastK(data, k, 0, data.Length - 1); if (first > -1 && last > -1) { number = last - first + 1; } } return number; }
三、單元測試
3.1 測試用例
// 查找的數字出現在數組的中間 [TestMethod] public void GetNumberTest1() { int[] data = { 1, 2, 3, 3, 3, 3, 4, 5 }; int actual = NumberOfKHelper.GetNumberOfK(data, 3); Assert.AreEqual(actual, 4); } // 查找的數組出現在數組的開頭 [TestMethod] public void GetNumberTest2() { int[] data = { 3, 3, 3, 3, 4, 5 }; int actual = NumberOfKHelper.GetNumberOfK(data, 3); Assert.AreEqual(actual, 4); } // 查找的數組出現在數組的結尾 [TestMethod] public void GetNumberTest3() { int[] data = { 1, 2, 3, 3, 3, 3 }; int actual = NumberOfKHelper.GetNumberOfK(data, 3); Assert.AreEqual(actual, 4); } // 查找的數字不存在 [TestMethod] public void GetNumberTest4() { int[] data = { 1, 3, 3, 3, 3, 4, 5 }; int actual = NumberOfKHelper.GetNumberOfK(data, 2); Assert.AreEqual(actual, 0); } // 查找的數字比第一個數字還小,不存在 [TestMethod] public void GetNumberTest5() { int[] data = { 1, 3, 3, 3, 3, 4, 5 }; int actual = NumberOfKHelper.GetNumberOfK(data, 0); Assert.AreEqual(actual, 0); } // 查找的數字比最后一個數字還大,不存在 [TestMethod] public void GetNumberTest6() { int[] data = { 1, 3, 3, 3, 3, 4, 5 }; int actual = NumberOfKHelper.GetNumberOfK(data, 6); Assert.AreEqual(actual, 0); } // 數組中的數字從頭到尾都是查找的數字 [TestMethod] public void GetNumberTest7() { int[] data = { 3, 3, 3, 3 }; int actual = NumberOfKHelper.GetNumberOfK(data, 3); Assert.AreEqual(actual, 4); } // 數組中的數字從頭到尾只有一個重復的數字,不是查找的數字 [TestMethod] public void GetNumberTest8() { int[] data = { 3, 3, 3, 3 }; int actual = NumberOfKHelper.GetNumberOfK(data, 4); Assert.AreEqual(actual, 0); } // 數組中只有一個數字,是查找的數字 [TestMethod] public void GetNumberTest9() { int[] data = { 3 }; int actual = NumberOfKHelper.GetNumberOfK(data, 3); Assert.AreEqual(actual, 1); } // 數組中只有一個數字,不是查找的數字 [TestMethod] public void GetNumberTest10() { int[] data = { 3 }; int actual = NumberOfKHelper.GetNumberOfK(data, 2); Assert.AreEqual(actual, 0); } // 魯棒性測試,數組空指針 [TestMethod] public void GetNumberTest11() { int actual = NumberOfKHelper.GetNumberOfK(null, 0); Assert.AreEqual(actual, 0); }
3.2 測試結果
(1)測試通過情況

(2)代碼覆蓋率

