力扣 - 劍指 Offer 39. 陣列中出現次數超過一半的數字

linzeliang發表於2021-10-21

題目

劍指 Offer 39. 陣列中出現次數超過一半的數字

思路1(排序)

  • 因為題目說一定會存在超過陣列長度一半的一個數字,所以我們將陣列排序後,位於length/2位置的一定是眾數

程式碼

class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length/2];
    }
}

複雜度分析

  • 時間複雜度:\(O(NlogN)\)
  • 空間複雜度:\(O(1)\)

思路2(雜湊表)

  • 遍歷一遍陣列,將陣列的值作為鍵,出現的次數作為值,存放到雜湊表中
  • 遍歷雜湊表,找到出現次數最多的那一個鍵值對就是我們要的眾數

程式碼

class Solution {
    public int majorityElement(int[] nums) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int n : nums) {
            map.put(n, map.getOrDefault(n, 0) + 1);
        }

        int max = 0;
        int res = 0;
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (max < entry.getValue()) {
                max = entry.getValue();
                res = entry.getKey();
            }
        }
        return res;
    }
}

複雜度分析

  • 時間複雜度:\(O(N)\)
  • 空間複雜度:\(O(N)\)

思路3(分治)

  • 將陣列從中間開始不斷分成兩份,直到只剩下一個元素時候開始返回
  • 如果left和right出現的頻率一樣,直接返回
  • 計算left和right在lo~hi範圍內出現的頻率,將高頻率的返回
  • 為什麼可以用這個方法?比如有[1, 2, 3, 2, 2, 2, 5, 4, 2],共有9個元素,共會被分成5份(每份一個或者兩個元素),然後從每份中再獲取一個值,共獲取5個值,又因為眾數的個數要大於陣列的長度,所以,眾數一定會至少被選中一個,只要被選中一個,那麼我們的countInRange函式會根據範圍幫我們計算出最多出現的元素個數即眾數

程式碼

class Solution {
    public int majorityElement(int[] nums) {
        return majorityElementRec(nums, 0, nums.length-1);
    }

    public int majorityElementRec(int[] nums, int lo, int hi) {
        // 如果只有一個元素,那麼直接返回
        if (lo == hi) {
            return nums[lo];
        }

        // 獲取中間值
        int mid = lo + (hi - lo) / 2;
        // 遞迴左邊和右邊,直到剩下一個元素
        int left = majorityElementRec(nums, lo, mid);
        int right = majorityElementRec(nums, mid+1, hi);

        // 相等只需要返回一個
        if (left == right) {
            return left;
        }

        // 計算left和right在lo~hi範圍中的出現的次數
        int leftCount = countInRange(nums, left, lo, hi);
        int rightCount = countInRange(nums, right, lo, hi);

        // 返回出現次數多的數
        return leftCount > rightCount ? left : right;
    }

    // 統計目標數字在指定範圍出現的次數
    public int countInRange(int[] nums, int target, int lo, int hi) {
        int count = 0;
        for (int i = lo; i <= hi; i++) {
            if (nums[i] == target) {
                count++;
            }
        }
        return count;
    }
}

複雜度分析

  • 時間複雜度:\(O(NlogN)\)
  • 空間複雜度:\(O(logN)\),遞迴過程中使用了額外的棧空間

思路4(摩爾投票法)

  • 眾數記為+1,把其他數記為-1,將它們全部加起來,和最終會大於0
  • 假設眾數記為res,票數記為votes,假設有這麼一個陣列:[1, 2, 3, 2, 2, 2, 5, 4, 2],使用摩爾投票法的過程如下:
    • i=0時:因為當前票數為0,所以將1賦值給res,同時票數也加一。此時res=1 votes=1
    • i=1時:2不等於1,所以票數要減1,此時res=1 votes=0
    • i=2時:因為票數為0,所以眾數res要指向當前的3,然後票數加一,此時res=3 votes=1
    • i=3時:2不等於3,所以票數要減1,此時res=3 votes=0
    • i=4時:因為票數為0,所以眾數res要指向當前的2,然後票數加一,此時res=2 votes=1
    • i=5時:由於2=2,所以票數加一,此時res=2 votes=2
    • i=6時:5不等於2,所以票數要減1,此時res=2 votes=1
    • i=7時:4不等於2,所以票數要減1,此時res=2 votes=0
    • i=8時:因為票數為0,所以眾數res要指向當前的2,然後票數加一,此時res=2 votes=1
    • 最後就直接輸出res即為眾數

程式碼

class Solution {
    public int majorityElement(int[] nums) {
        // 代表結果的眾數
        int res = nums[0];
        // 統計票數
        int votes = 0;

        for (int i = 0; i < nums.length; i++) {
            // 剛開始是0票,所以把陣列的第一個元素作為眾數
            // 如果以後的迴圈votes票數被抵消掉了為0,那麼下一個元素就作為眾數
            if (votes == 0) {
                res = nums[i];
            }
            // 和當前眾數相同的,那麼票數就加1
            if (res == nums[i]) {
                votes++;
            } else {
                // 如果和當前票數不同,票數就被抵消掉了一個
                votes--;
            }
        }
        
        return res;
    }
}

複雜度分析

  • 時間複雜度:\(O(N)\)
  • 空間複雜度:\(O(1)\)

相關文章