每日一練(20):陣列中出現次數超過一半的數字

加班猿發表於2022-02-22

title: 每日一練(20):陣列中出現次數超過一半的數字

categories:[劍指offer]

tags:[每日一練]

date: 2022/02/16


每日一練(20):陣列中出現次數超過一半的數字

陣列中有一個數字出現的次數超過陣列長度的一半,請找出這個數字。

你可以假設陣列是非空的,並且給定的陣列總是存在多數元素。

示例 1:

輸入: [1, 2, 3, 2, 2, 2, 5, 4, 2]

輸出: 2

限制:

1 <= 陣列長度 <= 50000

來源:力扣(LeetCode)

連結:https://leetcode-cn.com/probl...

方法一:雜湊表

思路

我們知道出現次數最多的元素大於n/2次,所以可以用雜湊表來快速統計每個元素出現的次數。

演算法

我們使用雜湊對映(HashMap)來儲存每個元素以及出現的次數。對於雜湊對映中的每個鍵值對,鍵表示一個元素,值表示該元素出現的次數。

我們用一個迴圈遍歷陣列 nums 並將陣列中的每個元素加入雜湊對映中。在這之後,我們遍歷雜湊對映中的所有鍵值對,返回值最大的鍵。我們同樣也可以在遍歷陣列 nums 時候使用打擂臺的方法,維護最大的值,這樣省去了最後對雜湊對映的遍歷。

複雜度分析

  • 時間複雜度:O(n),其中 n 是陣列 nums 的長度。我們遍歷陣列 nums 一次,對於 nums 中的每一個元素,將其插入雜湊表都只需要常數時間。如果在遍歷時沒有維護最大值,在遍歷結束後還需要對雜湊表進行遍歷,因為雜湊表中佔用的空間為 O(n)(可參考下文的空間複雜度分析),那麼遍歷的時間不會超過 O(n)。因此總時間複雜度為 O(n)。
  • 空間複雜度:O(n)。雜湊表最多包含 n - n/2個鍵值對,所以佔用的空間為 O(n)。這是因為任意一個長度為 n 的陣列最多隻能包含 n 個不同的值,但題中保證 nums 一定有一個眾數,會佔用(最少)n/2 + 1 個數字。因此最多有n - (n/2 +1)個不同的其他數字,所以最多有 n - n/2個不同的元素。
int majorityElement(vector<int>& nums) {
    unordered_map<int, int> counts;
    int majority = 0, cnt = 0;
    for (int num : nums) {
        ++counts[num];
        if (counts[num] > cnt) {
            majority = num;
            cnt = counts[num];
        }
    }
    return majority;
}

方法二:排序

思路

如果將陣列 nums 中的所有元素按照單調遞增或單調遞減的順序排序,那麼下標為 n/2 的元素(下標從 0 開始)一定是眾數。

演算法

對於這種演算法,我們先將 nums 陣列排序,然後返回上文所說的下標對應的元素。

複雜度分析

  • 時間複雜度:O(n log n)。將陣列排序的時間複雜度為 O(n log n)。
  • 空間複雜度:O(log n)。如果使用語言自帶的排序演算法,需要使用 O(log n) 的棧空間。如果自己編寫堆排序,則只需要使用 O(1) 的額外空間。
int majorityElement(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    return nums[nums.size() / 2];
}

方法三:隨機化

思路

因為超過 n/2 的陣列下標被眾數佔據了,這樣我們隨機挑選一個下標對應的元素並驗證,有很大的概率能找到眾數。

演算法

由於一個給定的下標對應的數字很有可能是眾數,我們隨機挑選一個下標,檢查它是否是眾數,如果是就返回,否則繼續隨機挑選。

int majorityElement(vector<int>& nums) {
    while (true) {
        int candidate = nums[rand() % nums.size()];
        int count = 0;
        for (int num : nums) {
            if (num == candidate) {
                ++count;
            }
            if (count > nums.size() /2)
        }
    }
}

相關文章