一、概述
输入一个数组,找到它所有出现次数大于n/3的元素。
二、分析
不会。之前那个主元素的题忘记了。这次变种题就把我卡住了。
要求时间复杂度O(n)空间复杂度O(1),肯定不是哈希这种做法。
直接去看解析。
用的是摩尔投票法。上一道主元素题也是这样。它的具体思路是什么呢?按我的理解,就是找到当前以及之前所有元素中最大的元素。按该博客的说法,
数组中从candidate被赋值到count减到0的那一段可以被去除,余下部分的多数元素依然是原数组的多数元素。
这句话怎么理解呢?就是我们维护一个当前元素及所有之前元素的一个摘要,这个摘要记录了之前所有元素中数量最多的元素。之后就看这个摘要就行了,不用去管前面了。
这个算法核心就是形成摘要。以下面这个序列举例说明:
11132224444
本题要求我们找到的主元素要求是大于三分之一,那么最多有两个主元素。我们维护四个值:两个候选值m1和m2,它们的相对出现次数num1和num2,初始化为0。
首先读入第一个元素1,由于num1为0,因此令第一个候选值m1为1,num1为1;
接下来读入1,由于m1为1,因此num1加1,num1为2;
接下来读入1,由于m1为1,因此num1加1,num1为3;
接下来读入3,由于num2为0,因此令第二个候选值m2为3,num2为1;
接下来读入2,由于num1和num2均不为0,且2既不是m1也不是m2,因此令num1和num2都减1;
这一步之前,已经读入的序列为1113,m1=1,num1=3,m2=3,num2=1;这个摘要很好的描述了1113的性质;
这一步之后,已经读入的序列为11132,m1=1,num1=2,m2=3,num2=0;这个摘要很好的描述了11132的性质,因为出现了2,因此1和3的相对出现次数都减了1,11132的摘要可以是2个1,0个3,也就是11132可以直接看作是11;
接下来读入2,由于num2为0,因此m2变为2,num2为1;
接下来读入2,由于m2为2,因此num2加1,num2为2;
接下来读入4,由于num1和num2均不为0,且4既不是m1也不是m2,因此令num1和num2都减1,num1=1,num2=1,此时序列为11132224,之前压缩为1122,那么现在是11224,摘要为1个1,1个2;
接下来读入4,由于num1和num2均不为0,且4既不是m1也不是m2,因此令num1和num2都减1,num1=0,num2=0,此时序列为111322244,之前压缩为1122,那么现在是112244,摘要为0个1,0个2,看,现在1和2仍然是主元素候选;
接下来读入4,这时候,事情开始起变化。由于num1和num2均为0,且4既不是m1也不是m2,因此令m1为4,num1=1,此时序列为1113222444,之前压缩为1122,那么现在是1122444,摘要为1个4,0个2,看,现在4和2是主元素候选;
接下来读入4,由于m1为4,因此num1加一,num1为2;
最终的摘要为2个4,0个2。
这可以这样看:对于11132224444,首先把数量为1的3减去,压缩为11224444,然后把数量为2的1和2减去,压缩为44,这就是最终的摘要。
有的人可能要问,把数量为1的3减去这一步,为什么4不减去一个?根据上面的过程没有减,但实际减了也不影响结果。压缩为44或者压缩为4,最终主元素都是4,没有区别。
这就是摩尔投票法的总体思想。使用呢四个数来抽取前n个数的主元素信息。
三、总结
很著名的寻找主元素算法。这是其中一个变体。
PS:代码如下:
class Solution {
public:
vector<int> majorityElement(vector<int>& nums) {
int m1=0,m2=0,num1=0,num2=0,n=nums.size();
for(auto i:nums)
{
if(i==m1)
num1++;
else if(i==m2)
num2++;
else if(num1==0)
{
m1=i;
num1++;
}
else if(num2==0)
{
m2=i;
num2++;
}
else
{
num1--;
num2--;
}
}
num1=0;
num2=0;
for(auto i:nums)
{
if(i==m1)
num1++;
if(i==m2)
num2++;
}
vector<int> res;
if(num1>n/3)
res.push_back(m1);
if(num2>n/3&&m1!=m2)
res.push_back(m2);
return res;
}
};