首先从一个leetcode问题开始:169. 多数元素
题目要求就是统计一个数组中超过半数的元素,题目已经确保输入的元素中必然有一个符合要求。
这个问题可以有很多解法,比如排序,使用map统计每一个元素的数量等,但是时间复杂度都超过O(n)。摩尔投票法的出现能使得时间复杂度将为O(n)。
摩尔投票法(Boyer-Moore major vote algorithm)的具象理解
我们已知一个数组中有一个元素的数量超过了其他元素数量的总和(大于一半),但是不知道是哪一个元素。那么我们可以这样想,把数组看成一个投票箱,下标 i 即为 num[i] 投出了支持票,我们的目标很明确,我们不关心每个参与竞选的候选人的得票数,我们只需要知道是哪一个候选人的得票数超过了一半,我们可以假定候选人 i=0 支持的 nums[0] 是候选人,那么他需要满足他的得票比其他任何人都多,我们可以这样操作,我们统计他的得票count。我们顺序扫描每个人投出的票,如果是支持他的,则将count 加 1,否则就将 count 减一,当count数量降为0时,就意味着没人支持他了,我们就将候选人换成当前投票者i支持的候选人nums[i], 由于已知有一个支持者的得票数超过了一半,那么无论剩下的人支持谁,都不能撼动他到最后的。想象一下最恶劣的情况,假设这个候选人出现的数组的头部,那么他的得票数因为反对者都在他后面(1,2,3,…),即使时这样,他的支持者仍然能确保成为最后仅存的候选人,那么无论这个候选人出现在什么位置,依照上述规则投票,最后的候选人仍然是他,不可能会是其他候选人,因为其他候选人都会被这个最后的候选人的支持者反对出去(因为他们的数量足够)。
class Solution {
public int majorityElement(int[] nums) {
int count = 0;
int candidate = nums[0];
for (int num : nums) {
if (count == 0) {
candidate = num;
}
count += (num == candidate) ? 1 : -1;
}
return candidate;
}
}
当然,上述算法求超过一半的数有个前提,就是输入的数据中一定要满足有一个元素的数量要超过一般,否则就无法得出上述结论。如果没有做出上述保证,我们可以在最后对候选人的得票数做一个统计,如果超过一半,则是说明存在一个数量超过一半的元素,并且就是当前元素,否则,就不存在这样的元素。
摩尔投票法就是用来解决求众数问题的
这个问题和上面的问题有所不同,他要求两个这样的数字,但是思路是一样的。
class Solution {
public List<Integer> majorityElement(int[] nums) {
int cand1 = nums[0];
int count1 = 0;
int cand2 = nums[0];
int count2 = 0;
List<Integer> ans = new ArrayList<>();
for (int num : nums) {
if (cand1 == num) {
++count1;
continue;
}
if (cand2 == num) {
++count2;
continue;
}
if (count1 == 0) {
cand1 = num;
++count1;
continue;
}
if (count2 == 0) {
cand2 = num;
++count2;
continue;
}
--count1;
--count2;
}
count1 = 0;
count2 = 0;
for (int num : nums) {
if (cand1 == num) ++count1;
else if (cand2 == num) ++count2;
}
if (count1 > nums.length / 3) ans.add(cand1);
if (count2 > nums.length / 3) ans.add(cand2);
return ans;
}
}
从这个问题推广出来,如果要选择出得票数超过1/(m+1)的元素,那么这样的元素最多有m个。在循环结束后需要判段这m个候选者是否得到了超过1/(m+1)的票数。