劍指Offer面試題:32.數字在排序數組中出現的次數


一、題目:數字在排序數組中出現的次數

題目:統計一個數字在排序數組中出現的次數。例如輸入排序數組{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)代碼覆蓋率

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM