劍指Offer-39-數字在排序陣列中出現的次數

Special__Yang發表於2018-08-12

題目

統計一個數字在排序陣列中出現的次數。

解析

預備知識

在排序陣列中,高效的查詢指定數字可以採用二分查詢,該方法的複雜度為O(logn),它可以看做有序陣列中查詢的標配!思路如下:
1. 首先有個指標start,end分別指向陣列的開頭和結尾,k為待查數字。
2. 判斷start是否大於end,若是則結束,否則進行第三步
3. 求出中間索引mid = start + ((end - start) >> 1)
4. 判斷mid所指向的值與k比較,若大於,則說明k位於mid前面,所以更新end = mid - 1,跳到第2步;若小於則說明,k位於mid後面,所以更新start = mid + 1,跳到第2步;若相等,則說明找到,直接返回當前mid索引即可

    private static int binarySearch(int[] array, int k) {
        int start = 0, end = array.length - 1;
        while(start <= end) {
            int mid = start + ((end - start) >> 1);
            if(array[mid] < k) {
                start = mid + 1;
            } else if(array[mid] > k) {
                end = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

思路一

既然我們要統計指定數字在陣列出現的次數,我們先根據二分查詢找到該數字的位置。因為在排序陣列中,相同的數字是連續在一起的,所以對得到的位置向前和向後搜查並統計即可。

    public static int GetNumberOfK3(int [] array , int k) {
        if(array == null || array.length == 0) {
            return 0;
        }
        int index = binarySearch(array, k);
        int count = 0;
        if(index != - 1) {
            count++;
            //向前搜查
            for(int i = index - 1; i >= 0; i--) {
                if(array[i] == k) {
                    count++;
                }
            }
            //向後搜查
            for(int i = index + 1; i < array.length; i++) {
                if(array[i] == k) {
                    count++;
                }
            }
        }
        return count;
    }

思路二

但是思路一有個弊端,我們雖然利用高效的二分找到該數字的任意的一個位置,但是如果連續的數字有很多,演算法複雜度退化為O(n),顯然是不能接受。我們發現只要確定了數字出現的第一次的位置和最後一次的位置,做差即可確定其出現的總次數了。所以問題轉化為如何求數字第一次出現的位置和最後一次的位置。
求數字第一次出現的位置:改變二分查詢
對於經典二分查詢中,當mid所指的內容等於給定的值的時候,我們令end = mid - 1;這樣可以進一步向前縮小搜查的範圍,因為當前的搜查到的位置可能不是第一次出現,所以要繼續向該位置之前查詢。而最後結束迴圈的時候,start指向的位置就是數字第一次出現的位置或者為剛剛大於該值的位置(這種情況是陣列中不存在該值)。

    /**
     * 變形的二分查詢
     * @param array
     * @param k
     * @return start指向k第一次出現的位置,或者指向剛剛大於k的位置(不存在k)
     */
    public static int findFirst(int[] array, int k) {
        int start = 0, end = array.length - 1;
        while(start <= end) {
            int mid = start + ((end - start) >> 1);
            if(array[mid] < k) {
                start = mid + 1;
            } else {
                end = mid - 1;
            }
        }
        return start;
    }

求數字最後一次出現的位置:改變二分查詢
對於經典二分查詢中,當mid所指的內容等於給定的值的時候,我們令start = mid + 1;這樣可以進一步向前縮小搜查的範圍,因為當前的搜查到的位置可能不是最後一次出現,所以要繼續向該位置之後查詢。而最後結束迴圈的時候,end指向的位置就是數字最後一次出現的位置或者為剛剛小於該值的位置(這種情況是陣列中不存在該值)。

    /**
     * 變形的二分查詢
     * @param array
     * @param k
     * @return end指向k最後一次出現的位置,或者指向剛剛小於k的位置(不存在k)
     */
    public static int findLast(int[] array, int k) {
        int start = 0, end = array.length - 1;
        while(start <= end) {
            int mid = start + ((end - start) >> 1);
            if(array[mid] > k) {
                end = mid - 1;
            } else {
                start = mid + 1;
            }
        }
        return end;
    }

等於得到的first和last索引後,我們取差值 + 1即可得到出現的次數。
1. 比如若存在2個k,first位置為2,last位置為3,那麼次數為:3 - 2 + 1 = 2
2. 比如不存在k, first位置為3, last位置為2, 那麼次數為:2 - 3 + 1 = 0

顯然,k是否存在都符合差值 + 1來求長度。

    public static int GetNumberOfK2(int [] array , int k) {
        if(array == null || array.length == 0) {
            return 0;
        }
        int firstOcur = findFirst(array, k);
        int lastOcur = findLast(array, k);
        return lastOcur - firstOcur + 1;
    }

總結

有序陣列的查詢首選二分查詢。

相關文章