那些算頻率的演算法,現在都怎麼樣了?

nanchen2251發表於2019-03-03

面試 19:輸出陣列中出現次數超過一半的數字(劍指 Offer 26 題)

上一篇推文給大家留下的習題來自於《劍指 Offer》第 29 題:陣列中超過一半的數字,不知道各位去思考了麼?

面試題:陣列中有一個數字出現的次數超過陣列長度的一半,請找出這個數字並輸出。比如 {1,2,3,2,2,2,1} 中 2 的次數是 4,陣列長度為 7,所以輸出 2。要求不能修改輸入的陣列。

準備測試用例

這道題能思考到的測試用例比較簡單。

  1. 輸入符合條件的陣列,檢視列印是否滿足情況;
  2. 輸入不符合條件的陣列,檢視列印;
  3. 輸入只有一個元素的陣列,檢視列印;
  4. 輸入無效陣列,檢視列印;

思考程式邏輯

第二步便是我們的思考程式邏輯了,題目要求查詢出現次數超過一半的數字。比較容易想到的思路是直接對陣列排序,那中間那個值就是我們想要的值,但這樣的想法明顯排序後會對輸入的陣列順序有影響,所以我們可能需要換一種思路。

再看一遍題幹,我們不難思考到,我們是否可以對每個數字進行計數,最後返回計數次數最多的值。儲存次數採用 map 做對映處理。

public class Test19 {
    private static int moreThanHalfNums(int[] nums) {
        if (nums == null || nums.length == 0)
            throw new RuntimeException("the length of array must be large than 0");
        int len = nums.length;
        Map<Integer, Integer> map = new HashMap<>();

        for (int num : nums) {
            if (map.containsKey(num))
                map.put(num, map.get(num) + 1);
            else
                map.put(num, 1);
        }
        int times = len / 2;
        // 查詢 map 中 value 最大的值
        for (Entry<Integer, Integer> entry : map.entrySet()) {
            if (entry.getValue() > times)
                return entry.getKey();
        }
        throw new RuntimeException("invalid input!");
    }

    public static void main(String[] args) {
        int[] nums1 = {1, 2, 3, 2, 2, 4, 2, 2, 5};
        System.out.println(moreThanHalfNums(nums1));
        int[] nums2 = {1};
        System.out.println(moreThanHalfNums(nums2));
        int[] nums3 = {2, 1, 2, 1, 2, 2, 3, 2, 1};
        System.out.println(moreThanHalfNums(nums3));
        int[] nums4 = {1, 2, 3, 4, 5};
        System.out.println(moreThanHalfNums(nums4));
    }
}
複製程式碼

寫畢後進行測試用例的驗證,無不例外,目前都通過,於是我們把這樣的程式碼解法遞交給面試官。

面試官看了這樣的演算法,表示他更期待的是不使用任何輔存空間的演算法。於是我們得換個角度思考。

陣列中有一個數字出現的次數超過陣列長度的一半,也就是說它出現的次數比其他所有數字出現次數的和還要多。因此我們可以考慮在遍歷陣列的時候儲存兩個值: 一個是陣列中的一個數字, 一個是次數。當我們遍歷到下一個數字的時候,如果下一個數字和我們之前儲存的數字相同,則次數加 1 ;如果下一個數字和我們之前儲存的數不同,則次數減 1。如果次數為 0,我們需要儲存下一個數字,並把次數設為 1 。由於我們要找的數字出現的次數比其他所有數字出現的次數之和還要多,那麼要找的數字肯定是最後一次把次數設為 1 時對應的數字。

我們來看這樣的思路用程式碼怎麼實現。

public class Test19 {

    private static int moreThanHalfNums(int[] nums) {
        if (nums == null || nums.length == 0)
            throw new RuntimeException("the length of array must be large than 0");
        int result = nums[0];
        int times = 1;
        int len = nums.length;
        for (int i = 1; i < len; i++) {
            if (times == 0) {
                result = nums[i];
                times = 1;
            } else if (result == nums[i])
                times++;
            else
                times--;
        }
        times = 0;
        for (int num : nums) {
            if (num == result)
                times++;
        }
        if (times > len / 2)
            return result;
        throw new RuntimeException("invalid input!");
    }

    public static void main(String[] args) {
        int[] nums1 = {1, 2, 3, 2, 2, 4, 2, 2, 5};
        System.out.println(moreThanHalfNums(nums1));
        int[] nums2 = {1};
        System.out.println(moreThanHalfNums(nums2));
        int[] nums3 = {2, 1, 2, 1, 2, 2, 3, 2, 1};
        System.out.println(moreThanHalfNums(nums3));
        int[] nums4 = {1, 2, 3, 4, 5};
        System.out.println(moreThanHalfNums(nums4));
    }
}
複製程式碼

寫畢後,驗證測試用例,同樣全部通過。

本題最後的思路,希望大家刻意去思考和記憶一下,因為也許變一下題意,這樣的想法還可以用到。

課後習題

我們下一次推文的題目來源於《劍指 Offer》第 31 題:計算連續子陣列的最大和。

面試題:輸入一個整型陣列,陣列中有正數也有負數。陣列中一個或多個整數形成一個子陣列,求所有子陣列的和的最大值,要求時間複雜度為 O(n)。
比如輸入 {1, -2, 3, 10, -4, 7, 2, -5},能產生子陣列最大和的子陣列為 {3,10,-4,7,2},最大和為 18。


我是南塵,只做比心的公眾號,歡迎關注我。

南塵,GitHub 7k Star,各大技術 Blog 論壇常客,出身 Android,但不僅僅是 Android。寫點技術,也吐點情感。做不完的開源,寫不完的矯情,你就聽聽我吹逼,不會錯~

那些算頻率的演算法,現在都怎麼樣了?

相關文章