1. 程式人生 > >劍指Offer面試題:32.數字在排序陣列中出現的次數

劍指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)程式碼覆蓋率

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

Offer試題32.數字排序陣列出現次數

一、題目:數字在排序陣列中出現的次數 題目:統計一個數字在排序陣列中出現的次數。例如輸入排序陣列{1,2,3,3,3,3,4,5}和數字3,由於3在這個陣列中出現了4次,因此輸出4。 二、解題思路 2.1 直接運用二分查詢   既然輸入的陣列是排序的,那麼我們很自然地就能想到用二分查詢演算法。

Offer試題2.二維陣列的查詢

一、題目:二維陣列中的查詢 題目:在一個二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。     例如下面的二維陣列就是每行、每列都遞增排序。如果在這個陣列中查詢數字7,則返回true;

Offer試題28.連續子陣列的最大和

一、題目:連續子陣列的最大和 題目:輸入一個整型陣列,數組裡有正數也有負數。陣列中一個或連續的多個整陣列成一個子陣列。求所有子陣列的和的最大值。要求時間複雜度為O(n)。例如輸入的陣列為{1,-2,3,10,-4,7,2,-5},和最大的子陣列為{3,10,-4,7,2},因此輸出為該子陣列的和18。

Offer試題35.將字串轉換為數字

一、題目:將字串轉換為數字 題目:寫一個函式StrToInt,實現把字串轉換成整數這個功能。當然,不能使用atoi或者其他類似的庫函式。 二、程式碼實現   (1)考慮輸入的字串是否是NULL、空字串   (2)考慮輸入的字串是否包含正負號或者是否是隻包含正負號   (3)考慮輸入的字串是否

Offer試題7.旋轉陣列的最小數字

一、題目:旋轉陣列的最小數字 題目:把一個數組最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。輸入一個遞增排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。例如陣列{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該陣列的最小值為1。   這道題最直觀的解法並不難,從頭到尾遍歷陣列一次

offer 試題重建二叉樹

題目:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。 思路:二叉樹先序是根左右,中序 是左根右。所以先找到

offer 試題從尾到頭列印連結串列

題目:輸入一個連結串列,按連結串列值從尾到頭的順序返回一個ArrayList。 思路:有多種放法。(1)先反轉連結串列,再列印連結串列。(2)使用棧。 /** * struct ListNode { * int val; * struct ListNode *n

牛客網線上程式設計專題《offer-試題38》數字排序陣列出現次數

題目連結: https://www.nowcoder.com/practice/70610bf967994b22bb1c26f9ae901fa2?tpId=13&tqId=11190&tPage=2&rp=2&ru=/ta/coding-interviews&

Offer試題17.樹的子結構

一、題目:樹的子結構 題目:輸入兩棵二叉樹A和B,判斷B是不是A的子結構。例如下圖中的兩棵二叉樹,由於A中有一部分子樹的結構和B是一樣的,因此B是A的子結構。   該二叉樹的節點定義如下,這裡使用C#語言描述: public class BinaryTreeNode {

Offer試題31.兩個連結串列的第一個公共節點

一、題目:兩個連結串列的第一個公共節點 題目:輸入兩個連結串列,找出它們的第一個公共結點。   連結串列結點定義如下,這裡使用C#語言描述: public class Node { public int key; public Node

Offer試題22.二叉搜尋樹的後序遍歷序列

一、題目:二叉搜尋樹的後序遍歷序列 題目:輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷的結果。如果是則返回true,否則返回false。假設輸入的陣列的任意兩個數字都互不相同。   例如在下面的一顆二叉搜尋樹中,輸入陣列{5,7,6,9,11,10,8},則返回true,因為這個整數序列是

Offer試題14.連結串列的倒數第k個節點

PS:這是一道出境率極高的題目,記得去年參加校園招聘時我看到了3次,但是每次寫的都不完善。 一、題目:連結串列的倒數第k個節點 題目:輸入一個連結串列,輸出該連結串列中倒數第k個結點。為了符合大多數人的習慣,本題從1開始計數,即連結串列的尾結點是倒數第1個結點。例如一個連結串列有6個結點,從頭結點開始

Offer試題4.從尾到頭列印連結串列

一、題目:從尾到頭列印連結串列 題目:輸入一個連結串列的頭結點,從尾到頭反過來打印出每個結點的值。   到解決這個問題肯定要遍歷連結串列。遍歷的順序是從頭到尾的順序,可輸出的順序卻是從尾到頭。也就是說第一個遍歷到的結點最後一個輸出,而最後一個遍歷到的結點第一個輸出。這就是典型的“後進先出”,我

Offer試題10.數值的整數次方

一、題目:數值的整數次方 題目:實現函式double Power(doublebase, int exponent),求base的exponent次方。不得使用庫函式,同時不需要考慮大數問題。   在.NET Framework提供的BCL中,Math類實現了一個Pow方法,例如要求2的三次方,可

Offer試題6.用兩個棧實現佇列

一、題目:用兩個棧實現佇列 題目:用兩個棧實現一個佇列。佇列的宣告如下,請實現它的兩個函式appendTail和deleteHead,分別完成在佇列尾部插入結點和在佇列頭部刪除結點的功能。   原文是使用C++結合模板實現的定義,這裡我們採用C#結合泛型來實現這個佇列的定義,我們要實現的就是兩

Offer試題20.棧的壓入、彈出序列

一、題目:棧的壓入、彈出序列 題目:輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1、2、3、4、5是某棧的壓棧序列,序列4、5、3、2、1是該壓棧序列對應的一個彈出序列,但4、3、5、1、2就不可能是該壓棧序列的彈出序列。

Offer試題30.第一個只出現一次的字元

一、題目:第一個只出現一次的字元 題目:在字串中找出第一個只出現一次的字元。如輸入"abaccdeff",則輸出'b'。要求時間複雜度為O(n)。   最直觀的想法是從頭開始掃描這個字串中的每個字元。當訪問到某字元時拿這個字元和後面的每個字元相比較,如果在後面沒有發現重複的字元,則該字元就是隻出現

Offer試題34.翻轉單詞順序VS左旋轉字串

一、題目一:翻轉單詞順序 1.1 題目說明 題目一:輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字元的順序不變。為簡單起見,標點符號和普通字母一樣處理。例如輸入字串"I am a student.",則輸出"student. a am I"。 1.2 解題思路   第一步翻轉句子中所有的字

Offer試題23.二叉樹中和為某一值的路徑

一、題目:二叉樹中和為某一值的路徑 題目:輸入一棵二叉樹和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑。從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。例如輸入下圖中二叉樹和整數22,則打印出兩條路徑,第一條路徑包含結點10、12,第二條路徑包含結點10、5和7。   二叉

Offer試題11.列印1到最大的n位數

一、題目:列印1到最大的n位數 題目:輸入數字n,按順序打印出從1最大的n位十進位制數。比如輸入3,則打印出1、2、3一直到最大的3位數即999。 二、不同的解法 2.1 不假思索的解法   最容易想到的辦法是先求出最大的n位數,然後用一個迴圈從1開始逐個列印: static v