在做leetcode上的每日打卡题目时,看到了几道一系列的关于众数的问题,都可以用摩尔投票的方法来解答,做个小笔记
Leetcode第169题 多数元素
题目
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
解析
运用减而治之的思想,摩尔投票方法
这里题目假设了数组非空,且一定存在多数元素,因此忽略对这两种情况的检查
图摘自清华大学数据结构慕课电子讲义
算法
- 从前向后遍历整个向量,借助计数器c,记录maj与其他元素的数量差额
- 若当前元素A[i]与候选者maj相等,则计数器c++,反之c–
- 每当c归零,都意味着此时的前缀P可以剪除,此时将众数候选者maj改为新的当前元素A[i],并将计数器更新为1
算法结束后的maj就是待求众数(这里题目已经明确了众数一定存在,所以可得出此结论,否则还需遍历一次向量,确保maj出现的次数超过向量元素总数的一半)
int majorityElement(vector<int>& nums)
{
int majority;
for (int i = 0,c=0; i < nums.size(); i++) //c为众数和其他数的差额计数器
{
if (0 == c)
{
majority = nums[i];
c = 1;
}
else
{
majority == nums[i] ? c++ : c--;
}
}
return majority;
}
Leetcode第229题 求众数Ⅱ
题目
给定一个大小为 n 的数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。
说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1)。
解析
首先,超过n/k的众数最多只有k-1个。因此,对于此题,超过n/3的数最多只可能有2个。同样使用摩尔投票的方法和减而治之的思想,先随机选出两个候选者A、B,然后遍历数组进行相应比对。
注意,此题并没有给出向量一定存在众数的先决条件,同时也没给出向量非空的假设,可能出现空向量或向量只有一个元素的退化情况,因此要分别对上述情况做出判断
算法
- 判断输入向量是否为空或只有一个元素,若是则直接返回原输入向量
- 将候选者A,B初始化为任意两个数,同时设立两个差额计数器c1,c2并初始化为0
- 从前向后遍历整个向量:
若投A(当前元素等于A),则A的票数++(计数器c1++);结束当前循环,进入下一次循环
若投B(当前元素等于B),则B的票数++(计数器c2++);结束当前循环,进入下一次循环
若A、B都不投,则先检查此时A或B的票数是否减为0,若A或B是,则当前元素替代其成为新的候选者(同时该候选者的票数更新为1);若A、B两人的票数都不为0,那么A、B的票数均–(计数器c1–,c2–);结束当前循环,进入下一次循环 - 遍历结束后,会得到两个最终的候选者maj1、maj2,,此时还需再从头遍历一次数组,对maj1、maj2出现的次数进行计数,检查其各自出现次数是否满足超过n/3次的条件。注意在此次遍历中,当前元素与maj1、maj2的各自判等比较中间采用的是else if连接,避免由于maj1等于maj2时将二者同时计数+1,最后得出两个符合要求的数的错误结论
注意上述步骤的顺序不能错,否则遇到一些特殊情况(如向量中前两个元素相同)时会出错
vector<int> majorityElement(vector<int>& nums)
{
if (0 == nums.size() || 1 == nums.size()) //判断输入数组是否非空或只有一个元素
return nums;
vector<int> ans;
int maj1 = 0, maj2 = 0; //maj1,maj2初始化为任意两个数
int count1 = 0, count2 = 0;
for (int i = 0; i < nums.size(); i++)
{
if (maj1 == nums[i])
count1++;
else if (maj2 == nums[i])
count2++;
else if (0 == count1)
{
maj1 = nums[i];
count1 = 1;
}
else if (0 == count2)
{
maj2 = nums[i];
count2 = 1;
}
else
{
count1--;
count2--;
}
}
count1 = 0;
count2 = 0;
for (int num : nums) //由于不保证到底有几个超过1/3的数,可能为0,1,2个,因此还需重新遍历一遍,进行确认
{
if (maj1 == num)
count1++;
else if (maj2 == num) //对maj1和maj2的判断一定要用else if,避免由于maj1等于maj2时将二者同时计数+1,最后得出两个符合要求的数的错误结论
count2++;
}
if (count1 > nums.size() / 3)
ans.push_back(maj1);
if (count2 > nums.size() / 3)
ans.push_back(maj2);
return ans;
}