看这篇总结:【算法】摩尔投票法 找 多数元素_小威W的博客-CSDN博客
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:nums = [3,2,3]
输出:3
示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2
提示:
nums.length
nums[i]
<= 10^9这道题目是找出现频率高于n/2的元素,是投票法。
投票法(Boyer-Moore Voting Algorithm)是一种用于在数组中查找主要元素的算法,主要元素定义为一个元素出现次数超过数组长度的一半。它并不一定能找到频率最高的元素,例如在数组 [1, 2, 2, 3, 3, 3]
中,频率最高的元素是 3
,但没有元素出现次数超过数组长度的一半,因此投票法不会返回任何元素。如果数组 [1, 1, 2, 2, 3, 3, 3, 3]
中,频率最高的元素也是主要元素,这时投票法会返回元素 3
。
如果一个元素是数组的多数元素(出现次数超过数组长度的一半),那么即使我们把它和其他每个不同的元素一一抵消(每次都从数组中删除两个不同的元素),最后剩下的一定是这个多数元素。
class Solution {
public:
int majorityElement(vector<int>& nums) {
int ans=-1,count=0;
for(int num:nums){
if(count==0) ans = num;
//这样写的话,else会和直接相邻的上一个if组成if-else
if(num==ans) count++;
else count--;
}
return ans;//注意ans就是多数元素!
}
};
在C++中,else
语句总是与最近的一个未配对的if
语句进行配对。因此,上面写法中,else
语句是与第二个if
语句配对的,形成了一个if-else
结构。
上面是比较直观的写法,也可以换成另一种逻辑更清晰的:
class Solution {
public:
int majorityElement(vector<int>& nums) {
int ans=-1,count=0;
for(int num:nums){
if(count==0) ans = num;
count += (num==ans)?1:-1;
}
return ans;//注意ans就是多数元素!
}
};
数组中占比超过一半的元素称之为主要元素。给你一个 整数 数组,找出其中的主要元素。若没有,返回 -1
。请设计时间复杂度为 O(N)
、空间复杂度为 O(1)
的解决方案。
示例 1:
输入:[1,2,5,9,5,9,5,5,5]
输出:5
示例 2:
输入:[3,2]
输出:-1
示例 3:
输入:[2,2,1,1,1,2,2]
输出:2
本题是多数元素不一定存在的情况,也就是说,我们根据投票法找到元素之后,需要进行二阶段的判断,判断这个元素出现次数是不是真的>n/2!
对抗阶段的代码与上一题 169.多数元素 相同,但是本题需要增加计数阶段,也就是验证元素出现次数。
class Solution {
public:
int majorityElement(vector<int>& nums) {
//对抗阶段,先用投票法抵消找出一个备选值
int ans=0,count=0;
for(int num:nums){
if(count==0) ans=num;//ans不需要赋特别大的初值因为会直接覆盖
count+= (num==ans)?1:-1;
}
//计数阶段,验证这个备选值是不是真的出现次数高于n/2
int cnt=0;
for(int num:nums){
if(num==ans) cnt++;
}
//判断次数是不是超过n/2
if(cnt>nums.size()/2){
return ans;
}
return -1;
}
};
给定一个大小为 n 的整数数组,找出其中所有出现超过 ⌊ n/3 ⌋
次的元素。
示例 1:
输入:nums = [3,2,3]
输出:[3]
示例 2:
输入:nums = [1]
输出:[1]
示例 3:
输入:nums = [1,2]
输出:[1,2]
提示:
1 <= nums.length <= 5 * 104
-10^9 <= nums[i] <= 10^9
这道题的问题是:在一个数组中找到所有出现次数大于n/3的元素。
大于n/3的元素,且不包含相等,也就是说一个数组里最多只有两个这样的元素。和基础的投票法思路相同,我们可以先预设两个备选值,再看后面的元素经过count的抵消,能不能继续保留这两个备选值。
算法分为两个阶段:
a1
和a2
。对于遍历到的每个元素num
,如果num
等于a1
或a2
,则增加a1
或a2
的计数;否则,如果a1
或a2
的计数为0,将num
设置为a1
或a2
,并将对应的计数设置为1;如果num
、a1
和a2
都不相等,并且a1
和a2
的计数都不为0,则num
、a1
和a2
互相抵消,a1
和a2
的计数都减1。a1
和a2
的出现次数。如果a1
或a2
的出现次数大于n/3,则将其添加到结果列表。class Solution {
public:
vector<int> majorityElement(vector<int>& nums) {
//对抗阶段,写法和上面的主要元素是一样的
int num1=0,count1=0;
int num2=0,count2=0;
for(int num:nums){
//先赋初值
if(count1==0) num1=num;
else if(count2==0) num2=num;
//累积频率,这里count2的更新是有问题的,count2最开始没有赋值是不能-1的
count1 += (num1==num)?1:-1;
count2 += (num2==num)?1:-1;
}
//计数阶段,验证是不是真的>n/3
int cnt1=0,cnt2=0;
for(int num:nums){
c1+=(num=nums1)?1:0;
c2+=(num=nums2)?1:0;
}
//存储结果
vector<int>result;
if(c1>num.size()/3) result.push_back(nums1);
if(c2>num.size()/3) result.push_back(nums2);
return result;
}
};
class Solution {
public:
vector<int> majorityElement(vector<int>& nums) {
//对抗阶段,因为题目提示中的最大值是10^9,因此设置初始值为1e9+1
int num1 = 1e9+1,cnt1=0;
int num2 = 1e9+1,cnt2=0;
for(int num:nums){//这种写法num就是nums[i]的数值,没有下标概念
if(num==num1) cnt1++;
else if(num==num2) cnt2++;
else if(cnt1==0){
num1=num;
cnt1++;
}
else if(cnt2==0){
num2=num;
cnt2++;
}
else{//出现新元素,且不等于任何一个候选,且不需要替代任何一个候选,就进行抵消
cnt1--;
cnt2--;
}
}
//对抗阶段结束后,num1就是第一个数字,num2就是第二个数字
cnt1=0;
cnt2=0;
for(int num:nums){
cnt1+=(num==num1)?1:0;
cnt2+=(num==num2)?1:0;
}
vector<int>result;
if(cnt1>nums.size()/3) result.push_back(num1);
if(cnt2>nums.size()/3) result.push_back(num2);
return result;
}
};
这个错误是因为最开始,对抗阶段的逻辑出现了错误。对抗阶段逻辑最开始的写法:
//对抗阶段,因为题目提示中的最大值是10^9,因此设置初始值为1e9+1
int num1 = 1e9+1,cnt1=0;
int num2 = 1e9+1,cnt2=0;
for(int num:nums){//这种写法num就是nums[i]的数值,没有下标概念
//这里的写法是错误的,有两个候选元素都需要累积!
if(cnt1==0){
num1=num;
cnt1++;
}
else if(cnt2==0){
num2=num;
cnt2++;
}
else if(num==num1) cnt1++;
else if(num==num2) cnt2++;
else{
cnt1--;
cnt2--;
}
}
这里的问题在于,本题我们有两个候选元素,我们必须先判断这两个候选元素自身能不能进行累积,才能判断count是否==0!
否则例如上图中{2,2}这种用例,只有一个候选元素在累积,那么另一个元素count=0的判定如果放在前面,就会导致原有的==num1的元素没有被累积到。
修改为先判定num==num1/num==num2
,再判断count==0的情况,即可
这两个问题之间的主要区别在于正在寻找的元素的数量。
在查找主要元素的问题中,出现次数超过了n/2的只可能有一个元素。在这种情况下,当count变为0时,可以安全地开始考虑一个新的候选元素,因为不可能有两个元素都出现次数超过n/2。所以当count为0时,可以肯定之前的元素不可能是主要元素,所以重新设置一个候选主要元素是合理的。
然而,当寻找的是出现次数超过n/3的元素时,一个数组有可能存在两个这样的元素。
所以,不能直接在count为0时就更改候选元素,必须先判断新元素是不是==num1/==num2!如果直接在count=0的时候更改候选元素,可能会导致另一个候选元素无法累积,例如{2,2}这样的情况。
也就是说,n/3的情况,我们需要同时追踪两个候选元素(num1和num2)并维护它们的count。出现一个新元素。首先进行两个候选元素的累积判断,才能再判断这两个候选元素的count是否为0,为0的时候进行替换。