题目是有一堆查询,可能有一些查询出现次数超过 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; }