Leetcode上面有這么一道難度為easy的算法題:找出一個長度為n的數組中,重復次數超過一半的數,假設這樣的數一定存在。O(n2)和O(nlog(n))(二叉樹插入)的算法比較直觀。Boyer–Moore majority vote algorithm在1980年提出,用O(1)空間和O(n)時間解決了這個問題。這個算法的思路:由於重復頻率超過 floor(n/2)的數字只有一個,等價於與其余數字出現頻率的差大於零。當遍歷整個數組時,使用變量candidate記錄當前重復次數最多的數,count計算candidate重復多余的次數。以下為具體實現:
int count = 0; int candidate; for(int i = 0; i < n; ++i) { if(count == 0) { candidate = a[i]; }
if(candidate == a[i])
++count;
else
--count; }
在遍歷過程中,當前元素與candidate相同則投支持票,否則投反對票。當count狀態為0時,說明之前的子數組中不存在重復次數超過一半的數,遍歷余下的數組成為原問題的子問題。若該數不一定存在,那么需要再一次遍歷數組,鑒證找到的元素是否符合條件。
進一步思考,若要返回出現次數大於k次的所有元素,即為iceburg query問題。iceburg query的想法其實可以向其名字一樣形象。假設將數組中所有元素轉化為histogram,高度為出現的頻率,那么每個筒子有高有低,就像冰山一樣。之后不斷的下降冰山,下降k次。那么剩下還留在水面上的就是滿足要求的元素。直接這樣求解問題需要多次遍歷數組內的元素O(log(n!) + log(nk))。
當然也可以遍歷兩次。由於滿足條件的元素出現次數大於k,那么整個數組中至多存在n/k個。因此在第一次遍歷的時候,維護一個數組a,若當前元素不存在數組中,則插入該元素和出現次數1。然后判斷數組大小是否超過n/k。如果超過則所有元素下降一個,並且除去出現次數為0的元素。第二次遍歷,查看是否a中的元素出現次數都大於k(因為滿足條件的元素個數可以小於n/k)。
unordered_map m;
// first pass
for(i = 0; i < n; ++i)
{
if(m.find(nums[i]) == m.end())
{
m.insert(pair<int, int>(nums[i], 1));
}
else
{
++m[ nums[i] ];
}
if(m.size() > n / k)
{
for(auto it = m.begin(); it != m.end();++it)
{
--(it -> second);
if(!(it -> second))
m.erase(it++);
}
}
}
// second pass
for(auto &x: m)
m -> second = 0;
for(i = 0; i < n; ++i)
{
++m[ nums[i] ];
if(m[nums[i]] > k)
{
v.push_back(nums[i]);
}
}
