【每日算法Day 90】5種方法:求解數組中出現次數超過一半的那個數

v2-7c796fbee4d23c0949f20dd57531bda9_b.jpg
論文中了,非常感謝各位的支持!

題目鏈接

LeetCode 面試題39. 數組中出現次數超過一半的數字[1]

題目描述

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

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

說明:

  • 1 <= 數組長度 <= 50000

示例1

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

題解

哈希表

這個方法最簡單,用哈希表記錄每個數字出現的次數,最後看哪個數字次數超過一半就行了。

時間複雜度 O(n) ,空間複雜度 O(n)

排序

對數組從小到大進行排序,那麼衆數一定在 nums[n/2] 處。爲什麼呢?

因爲排序後相同的數都連續了,所以衆數最左端的極限情況就是從下標 0 開始往後排,那麼因爲超過了一半,所以尾部下標一定會超過 n/2 。而最右端的極限情況就是從下標 n-1 往前排,因爲超過了一半,所以頭部下標也會在 n/2 之前。

綜上,衆數所在的區間一定會包含下標 n/2

時間複雜度 O(n \log n) ,空間複雜度 O(\log n)

隨機採樣

其實我第一個想到的方法反而是這個反常規的隨機採樣方法。因爲衆數超過了一半,所以採樣大概率會採到這個衆數。

那麼我們隨機採樣一個數,然後遍歷一遍數組看它的個數。如果個數超過了一半就是它了,否則繼續採樣,直到採到衆數。

平均時間複雜度 O(n) ,空間複雜度 O(1)

分治

如果把區間 [0, n-1] 平均分成兩半,那麼我們可以證明,原來的衆數在某一半區間裏依然是衆數。

爲什麼呢?反證法,假設兩半區間的衆數都不是原來的衆數,那麼在左半區間原來的衆數一定小於一半,右半區間也是的。加起來之後總數一定小於一半的,和條件是矛盾的。

所以我們遞歸求解兩半區間的衆數,然後看哪個數出現次數較多,衆數就是它了。

時間複雜度 O(n \log n) ,空間複雜度 O(\log n)

摩爾投票

這個方法我一開始也想到了,但是沒有想到這竟然有理論解釋,而且是大名鼎鼎的摩爾投票算法。

它的主要步驟是這樣的:

  • 初始化兩個變量, cand 表示候選人,cnt 表示贊同它的票數。
  • 如果 cnt = 0,那麼 cand 就設置爲當前的數字。
  • 如果 cand 等於當前數字,那麼票數 cnt 加一,否則票數減一。
  • 最後 cand 就是得票超過一半的衆數。

嚴格證明比較複雜,是一篇論文,這裏說個比較好理解的思路:

  • 如果當前候選人是衆數,那麼其他的衆數會支持自己,其他的數反對自己。但是因爲衆數超過了一半,所以衆數最後一定會當選。
  • 如果當前候選人不是衆數,那麼就慘了,其他的數和衆數全都會反對他。那反對票遠遠超過一半了,肯定會下臺,然後換候選人。
  • 上面兩種情況會在 cnt = 0 的時刻進行轉換,也就是換候選人。

時間複雜度 O(n) ,空間複雜度 O(1)

代碼

哈希表(c++)

        class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        unordered_map<int, int> mp;
        for (auto x : nums) mp[x]++;
        for (auto [k, v] : mp) {
            if (v > n/2) return k;
        }
        return -1;
    }
};

      

排序(c++)

        class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        return nums[n/2];
    }
};

      

隨機採樣(c++)

        class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        while (1) {
            int idx = rand() % n;
            int cnt = 0;
            for (auto x : nums) {
                if (x == nums[idx]) cnt++;
            }
            if (cnt > n/2) return nums[idx];
        }
        return -1;
    }
};

      

分治(c++)

        class Solution {
public:
    int numCount(vector<int>& nums, int x, int l, int r) {
        int cnt = 0;
        for (int i = l; i <= r; ++i) {
            if (nums[i] == x) cnt++;
        }
        return cnt;
    }

    int findMajority(vector<int>& nums, int l, int r) {
        if (l == r) return nums[l];
        int m = l+(r-l)/2;
        int ml = findMajority(nums, l, m);
        int mr = findMajority(nums, m+1, r);
        if (ml == mr) return ml;
        int cl = numCount(nums, ml, l, r);
        int cr = numCount(nums, mr, l, r);
        return cl < cr ? mr : ml;
    }

    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        return findMajority(nums, 0, n-1);
    }
};

      

摩爾投票(c++)

        class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int cand = 0, cnt = 0;
        for (auto x : nums) {
            if (!cnt) cand = x;
            if (x == cand) cnt++;
            else cnt--;
        }
        return cand;
    }
};

      

哈希表(python)

        class Solution:
    def majorityElement(self, nums):
        counts = collections.Counter(nums)
        return max(counts.keys(), key=counts.get)

      

排序(python)

        class Solution:
    def majorityElement(self, nums):
        nums.sort()
        return nums[len(nums)//2]

      

隨機採樣(python)

        import random

class Solution:
    def majorityElement(self, nums):
        majority_count = len(nums)//2
        while True:
            candidate = random.choice(nums)
            if sum(1 for elem in nums if elem == candidate) > majority_count:
                return candidate

      

分治(python)

        class Solution:
    def majorityElement(self, nums, lo=0, hi=None):
        def majority_element_rec(lo, hi):
            if lo == hi:
                return nums[lo]
            mid = (hi-lo)//2+lo
            left = majority_element_rec(lo, mid)
            right = majority_element_rec(mid+1, hi)
            if left == right:
                return left
            left_count = sum(1 for i in range(lo, hi+1) if nums[i] == left)
            right_count = sum(1 for i in range(lo, hi+1) if nums[i] == right)
            return left if left_count > right_count else right
        return majority_element_rec(0, len(nums)-1)

      

摩爾投票(python)

        class Solution:
    def majorityElement(self, nums):
        count = 0
        candidate = None
        for num in nums:
            if count == 0:
                candidate = num
            count += (1 if num == candidate else -1)
        return candidate

      
關注【算法碼上來】,每日算法乾貨馬上就來!

參考資料

[1]

LeetCode 面試題39. 數組中出現次數超過一半的數字: leetcode-cn.com/problem

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章