多數投票算法(Boyer-Moore Algorithm)詳解
寫在前面:我在刷LeetCode 169 時碰到了這個問題,並且在評論區找到了這個方法,不過我發現CSDN上對其進行解讀的博客大多停留在知其然而不知其所以然的層面,所以准備在此做一個較為詳細的解讀,重點在於介紹其原理。
問題描述
給定一個無序數組,有n個元素,找出其中的一個多數元素,多數元素出現的次數大於⌊ n/2 ⌋,注意數組中也可能不存在多數元素。
一般解法
先對數組排序,然后取中間位置的元素,再對數據掃描一趟來判斷此元素是否為多數元素。時間復雜度O(nlog(n)),空間復雜度O(1)。
使用一個hash表,對數組進行一趟掃描統計每個元素出現的次數,即可得到多數元素。時間復雜度O(n),空間復雜度O(n)。
Boyer-Moore 算法
該算法時間復雜度為O(n),空間復雜度為O(1),只需要對原數組進行兩趟掃描,並且簡單易實現。第一趟掃描我們得到一個候選節點candidate,第二趟掃描我們判斷candidate出現的次數是否大於⌊ n/2 ⌋。
第一趟掃描中,我們需要記錄2個值:
candidate,初值可以為任何數
count,初值為0
之后,對於數組中每一個元素,首先判斷count是否為0,若為0,則把candidate設置為當前元素。之后判斷candidate是否與當前元素相等,若相等則count+=1,否則count-=1。
python代碼:
candidate = 0
count = 0
for value in input:
if count == 0:
candidate = value
if candidate == value:
count += 1
else:
count -= 1
1
2
3
4
5
6
7
8
9
在第一趟掃描結束后,如果數組中存在多數元素,那么candidate即為其值,如果原數組不存在多數元素,則candidate的值沒有意義。所以需要第二趟掃描來統計candidate出現的次數來判斷其是否為多數元素。
代碼雖簡單,但我們不光要知其然,更要知其所以然,探究代碼背后的原理往往可以收獲更多。
原理解析
為了解析算法的原理,我們只要考慮存在多數元素的情況即可,因為第二趟掃描可以檢測出不存在多數元素的情況。
舉個例子,我們的輸入數組為[1,1,0,0,0,1,0],那么0就是多數元素。
首先,candidate被設置為第一個元素1,count也變成1,由於1不是多數元素,所以當掃描到數組某個位置時,count一定會減為0。在我們的例子中,當掃描到第四個位置時,count變成0.
count 值變化過程:
[1,2,1,0……
當count變成0時,對於每一個出現的1,我們都用一個0與其進行抵消,所以我們消耗掉了與其一樣多的0,而0是多數元素,這意味着當掃描到第四個位置時,我們已經最大程度的消耗掉了多數元素。然而,對於數組從第五個位置開始的剩余部分,0依然是其中的多數元素(注意,多數元素出現次數大於⌊ n/2 ⌋,而我們掃描過的部分中多數元素只占一般,那剩余部分中多數元素必然還是那個數字)。如果之前用於抵消的元素中存在非多數元素,那么數組剩余部分包含的多數元素就更多了。
類似的,假設第一個數字就是多數元素,那么當count減為0時,我們消耗掉了與多數元素一樣多的非多數元素,那么同樣道理,數組剩余部分中的多數元素數值不變。
這兩種情況證明了關鍵的一點:數組中從candidate被賦值到count減到0的那一段可以被去除,余下部分的多數元素依然是原數組的多數元素。我們可以不斷重復這個過程,直到掃描到數組尾部,那么count必然會大於0,而且這個count對應的candinate就是原數組的多數元素。
分布式Boyer-Moore
Boyer-Moore還有一個優點,那就是可以使用並行算法實現。相關算法可見Finding the Majority Element in Parallel
其基本思想為對原數組采用分治的方法,把數組划分成很多段(每段大小可以不相同),在每段中計算出candidate-count二元組,然后得到最終結果。
舉個例子,原數組為[1,1,0,1,1,0,1,0,0]
划分1:
[1,1,0,1,1] –> (candidate,count)=(1,3)
划分2:
[0,1,0,0] –> (candidate,count)=(0,2)
根據(1,3)和(0,2)可得,原數組的多數元素為1.
正因為這個特性,考慮若要從一個非常大的數組中尋找多數元素,數據量可能多大數百G,那么我們甚至可以用MapReduce的方式來解決這個問題。
參考
https://gregable.com/2013/10/majority-vote-algorithm-find-majority.html
The Boyer-Moore Majority Vote Algorithm
Finding the Majority Element in Parallel
48. 主元素 III
給定一個整型數組,找到主元素,它在數組中的出現次數嚴格大於數組元素個數的1/k。
樣例
例1:
輸入: [3,1,2,3,2,3,3,4,4,4] and k=3,
輸出: 3.
class Solution: """ @param nums: A list of integers @param k: An integer @return: The majority number """ def majorityNumber(self, nums, k): # write your code here if not nums: return None if k <= 0: return None ballot = {} for num in nums: if num in ballot: # in, +1 ballot[num] += 1 else: # not in if len(ballot)<k-1: # not full, add ballot[num] = 1 else: # full, all-1, del all 0 key_to_delete = [] for key in ballot: ballot[key]-=1 if ballot[key] == 0: key_to_delete.append(key) for key_d in key_to_delete: del ballot[key_d] majority = None for key in ballot: if nums.count(key)>len(nums)//k: majority = key break return majority