摩尔投票法
能够在线性时间复杂度和常数级空间复杂度下,找到一个数组中出现次数过半的数字。
算法流程:
- 从数组中随便选取一个数作为
candidate
(不妨选择第一个数),记其票数为vote = 1
- 遍历数组,对于每个数
x
- 若此时
vote = 0
,则x
成为新的candidate
,并重置vote = 1
- 若此时
vote > 0
- 若
x = candidate
,则vote
增加1
- 若
x != candidate
,则vote
减少1
- 若
- 若此时
- 遍历完成后,
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
,出现次数最多的数是2
,2
在打败1
之后,胜利果实被3
窃取了,并且最后只有一个2
,无法再让candidate
恢复成2
,可以观察,此时的2
的出现次数刚好等于一半,并没有过半,若此时在末尾再多一个2
(过半),则能成功将胜利夺取回来)
需要注意,只有当存在某个出现次数过半的数,摩尔投票算法求解出来的才是这个数;若都不存在出现次数过半的数,则摩尔投票求解出来的是一个无效的值(最终的candidate
也并不是出现次数最多的数,尝试样例1 1 1 2 2 2 3 2
)。
摩尔投票算法的时间复杂度为
O
(
n
)
O(n)
O(n),因为只需要遍历一次;空间复杂度为
O
(
1
)
O(1)
O(1),因为只需要2个变量candidate
和vote
。
在不使用摩尔投票算法的情况下,比较优秀的做法是使用哈希表统计每个数的出现次数,并实时更新答案,时间复杂度为 O ( n ) O(n) 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;
}
};