題目鏈接
LeetCode 面試題39. 數組中出現次數超過一半的數字[1]
題目描述
數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。
你可以假設數組是非空的,並且給定的數組總是存在多數元素。
說明:
- 1 <= 數組長度 <= 50000
示例1
輸入:
[1, 2, 3, 2, 2, 2, 5, 4, 2]
輸出:
2
題解
哈希表
這個方法最簡單,用哈希表記錄每個數字出現的次數,最後看哪個數字次數超過一半就行了。
時間複雜度 ,空間複雜度 。
排序
對數組從小到大進行排序,那麼衆數一定在 nums[n/2]
處。爲什麼呢?
因爲排序後相同的數都連續了,所以衆數最左端的極限情況就是從下標 0
開始往後排,那麼因爲超過了一半,所以尾部下標一定會超過 n/2
。而最右端的極限情況就是從下標 n-1
往前排,因爲超過了一半,所以頭部下標也會在 n/2
之前。
綜上,衆數所在的區間一定會包含下標 n/2
。
時間複雜度 ,空間複雜度 。
隨機採樣
其實我第一個想到的方法反而是這個反常規的隨機採樣方法。因爲衆數超過了一半,所以採樣大概率會採到這個衆數。
那麼我們隨機採樣一個數,然後遍歷一遍數組看它的個數。如果個數超過了一半就是它了,否則繼續採樣,直到採到衆數。
平均時間複雜度 ,空間複雜度 。
分治
如果把區間 [0, n-1]
平均分成兩半,那麼我們可以證明,原來的衆數在某一半區間裏依然是衆數。
爲什麼呢?反證法,假設兩半區間的衆數都不是原來的衆數,那麼在左半區間原來的衆數一定小於一半,右半區間也是的。加起來之後總數一定小於一半的,和條件是矛盾的。
所以我們遞歸求解兩半區間的衆數,然後看哪個數出現次數較多,衆數就是它了。
時間複雜度 ,空間複雜度 。
摩爾投票
這個方法我一開始也想到了,但是沒有想到這竟然有理論解釋,而且是大名鼎鼎的摩爾投票算法。
它的主要步驟是這樣的:
- 初始化兩個變量,
cand
表示候選人,cnt
表示贊同它的票數。 - 如果
cnt = 0
,那麼cand
就設置爲當前的數字。 - 如果
cand
等於當前數字,那麼票數cnt
加一,否則票數減一。 - 最後
cand
就是得票超過一半的衆數。
嚴格證明比較複雜,是一篇論文,這裏說個比較好理解的思路:
- 如果當前候選人是衆數,那麼其他的衆數會支持自己,其他的數反對自己。但是因爲衆數超過了一半,所以衆數最後一定會當選。
- 如果當前候選人不是衆數,那麼就慘了,其他的數和衆數全都會反對他。那反對票遠遠超過一半了,肯定會下臺,然後換候選人。
- 上面兩種情況會在
cnt = 0
的時刻進行轉換,也就是換候選人。
時間複雜度 ,空間複雜度 。
代碼
哈希表(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. 數組中出現次數超過一半的數字: https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/