题目是有一堆查询,可能有一些查询出现次数超过 N/3,希望能找出这些查询词。
思路是:
利用三色旗问题的思想把针对某个查询将数组分为3部分,小于,等于,大于,同时也得到前两个区间的右下标分别为small,equal:
1.如果equal-small>=n/3,很明显选中的这个查询出现了超过n/3次,否则该词出现次数肯定低于n/3。
2.然后我们再检查左右两边的大小是否还不小于n/3,如果是,则在该区间递归。
3.下面代码中简单的选取区间第一个值作为比较值,而实际上应当引入三位取中、五位取中或者random来选取这个比较值,大家可以自行修改。
另外为了简化问题,查询串也用整数来代替了。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int query[200];
int n;
int answer[3];
int nAns;
bool input()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>query[i];
nAns=0;
return n!=0;
}
void process(int start,int end)
{
int piv=query[start];
int small=start,equal=start,big=end;
while(equal<=big)
{
if (query[equal]==piv )
equal++;
else if (query[equal]<piv )
swap(query[small++],query[equal++]);
else
{
while(big>equal&&query[big]>piv)
big--;
swap(query[big],query[equal]);
big--;
}
}
if (equal-small>= n/3)
answer[nAns++]=piv;
if ( small -start >=n/3)
process(start,small-1);
if ( end-equal+1 >=n/3 )
process(equal,end);
}
int main()
{
while(input())
{
process(0,n-1);
for(int i=0;i<nAns;i++)
cout<< answer[i] <<endl;
}
}
之前就在想这个算法虽然能解,但是一直觉得效率应该不是O(N)的,因为一次PATITION之后还要在两边进行递归,也就是说并未像FINDKTH那样能减小一半的搜索空间,所以这个算法的效率应该不是O(N)。
因此后来借鉴用一个变量来解决“出现超过一半的数字”的方法,写了下面一个类似的算法,这次是用两个变量(因为最多只有两个能超过N/3)。
记得最后留下的不一定是答案,还需要CHECK一下。
这个算法无疑是O(n)的。
bool check(int k)
{
int cnt=0;
for(int i=0;i<n;i++)
if ( query[i]==k)
cnt++;
return cnt>=n/3;
}
void process2()
{
int num1=-1,time1=0;
int num2=-1,time2=0;
for (int i=0;i<n;i++)
{
if(num1==-1)
{
num1=query[i];
time1=1;
}
else if (query[i]==num1 )
time1++;
else if (num2==-1)
{
num2=query[i];
time2=1;
}
else if ( query[i]==num2)
time2++;
else
{
if ( --time1 == 0)
num1=-1;
if ( --time2==0 )
num2=-1;
if ( time1==0 && time2>0 )
{
num1=num2;
time1=time2;
num2=-1;
time2=0;
}
}
}
if ( num1!=-1 &&check(num1) )
answer[nAns++]=num1;
if (num2!=-1&&check(num2))
answer[nAns++]=num2;
}