摩尔投票法:求解出现次数过半的众数

118 阅读3分钟

摩尔投票法

能够在线性时间复杂度常数级空间复杂度下,找到一个数组中出现次数过半的数字。

算法流程

  1. 从数组中随便选取一个数作为candidate(不妨选择第一个数),记其票数为vote = 1
  2. 遍历数组,对于每个数x
    • 若此时vote = 0,则x成为新的candidate,并重置vote = 1
    • 若此时vote > 0
      • x = candidate,则vote增加1
      • x != candidate,则vote减少1
  3. 遍历完成后,candidate即为整个数组中出现次数过半的数字(若存在)

算法理解

关键点在于:若出现次数过半的数存在,那其他数的出现次数一定不过半

设这个出现次数过半的数为x,在遍历的过程中

  • 若当前candidate已经为x,则其他所有数加起来的出现次数都不会超过x的次数,则vote一定不会被减到0,或者中途被减到0后,之后又会被再加回来,所以candidate(几乎)能一直保持是x
  • 若当前candidate不为x,则在后续遍历到x时,会对vote进行减操作,而由于x的出现次数超过了一半,则最终这个vote一定会被减到0,然后candidate会被更换为x。(如果减到0后,胜利的果实被其他数窃取了,那只能说明众数的出现次数不过半,如1 1 1 2 2 2 3 2,出现次数最多的数是22在打败1之后,胜利果实被3窃取了,并且最后只有一个2,无法再让candidate恢复成2,可以观察,此时的2的出现次数刚好等于一半,并没有半,若此时在末尾再多一个2(过半),则能成功将胜利夺取回来)

需要注意,只有当存在某个出现次数过半的数,摩尔投票算法求解出来的才是这个数;若都不存在出现次数过半的数,则摩尔投票求解出来的是一个无效的值(最终的candidate也并不是出现次数最多的数,尝试样例1 1 1 2 2 2 3 2)。

摩尔投票算法的时间复杂度为 O(n)O(n),因为只需要遍历一次;空间复杂度为 O(1)O(1),因为只需要2个变量candidatevote

在不使用摩尔投票算法的情况下,比较优秀的做法是使用哈希表统计每个数的出现次数,并实时更新答案,时间复杂度为O(n)O(n),空间复杂度为O(n)O(n)。注意使用这种方法,也可以正确求出出现次数最多的数,而不必要求出现次数一定要过半。

所以,对于求解众数,当众数的出现次数超过总数的一半,可以用摩尔投票算法;否则,可选用哈希表进行统计。

参考 力扣 169.多数元素

// 摩尔投票
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int candidate = 0, vote = 0;
        for (int& i : nums) {
            if (vote == 0) {
                candidate = i;
                vote = 1;
            } else {
                if (candidate == i) vote++;
                else vote--;
            }
        }
        return candidate;
    }
};