问题:传说,Tango有一大“水王”,他不但喜欢发帖还会回复其他ID发的每个帖子。该“水王”的帖子数目超过了帖子总数的一半,如何用快速的方法找出这个“水王”的ID?
分析:首先想到的是对所有的ID进行排序,然后再扫描一遍排好序的ID列表,统计各个ID出现的次数。如果某个ID的次数超过总数的一半,那么就输出这个ID。这个算法的时间复杂度为O(N*logN+N).
如果ID列表是已经排好序,则不需要重复扫描整个列表了。如果一个ID出现的次数超过总数N的一半。那么无论水王的ID是什么,这个有序的ID列表的第N/2 项(从0开始编号)一定会是这个水王的ID。出去排序的的时间复杂度,后处理需要的时间为O(1)。
但是上面两个方法都需要先对ID列表进行排序,时间复杂度方面没有本质的改变。能否避免排序呢?
如果每次删除两个不同的ID(不管是谁的ID),那么,在剩下的ID列表中,“水王”的ID出现的次数仍然超过总数的一半。按照这个原理,就可以不断重复这个过程,把ID列表中的ID总数降低,从而得到问题的答案。这个算法的时间复杂度只需要O(N),并且只需要常数的额外内存。
算法实现: 数组中有个数字出现的次数超过了数组长度的一半。也就是说,有个数字出现的次数比其他所有数字出现次数的和还要多。因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1。 如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零,我们需要保存下一个数字,并把次数重新设为1。
下面,举二个例子:
- 第一个例子,5,5,5,5,1 :
不同的相消,相同的累积。遍历到第四个数字时,candidate 是5, nTimes 是4; 遍历到第五个数字时,candidate 是5, nTimes 是3; nTimes不为0,那么candidate就是超过半数的。
- 第二个例子,0,1,2,1,1:
开始时,保存candidate是数字0,ntimes为1,遍历到数字1后,与数字0不同,则ntime减1变为零,;接下来,遍历到数字2,2与1不同,candidate保存数字2,且ntimes重新设为1;继续遍历到第4个数字1时,与2不同,ntimes减一为零,同时candidate保存为1;最终遍历到最后一个数字还是1,与我们之前candidate保存的数字1相同,ntime加一为1。最后返回的是之前保存的candidate为1
代码如下:
int Find(int * ID, int N)
{
int candidate;
int nTimes,i;
for(i=nTimes=0;i<N;i++)
{
if(nTimes==0)
{
candidate=ID[i];
nTimes=1;
}
else
{
if(candidate==ID[i])
nTimes++;
else
nTimes--;
}
}
return candidate;
}
例如:a[]={0,4,4,1,4,4,5,4},输出为4。
扩展问题:若三个发帖很多的ID,他们的发帖数目都超过了帖子总数目N的1/4,怎么从发贴的ID中快速找出他们的ID?
typedef int Type;
Type candidate1 ;
Type candidate2;
Type candidate3;
void Find(Type* ID, int N)
{
int nTimes1 = 0 ;
int nTimes2 = 0 ;
int nTimes3 = 0 ;
int i;
for( i = 0; i < N; i++)
{
if (nTimes1 == 0)
{
candidate1 = ID[i],nTimes1 = 1;
}
else
{
if (candidate1 == ID[i])
{
nTimes1++;
}
else
if (nTimes2 == 0)
{
candidate2 = ID[i],nTimes2 = 1;
}
else
{
if (candidate2 == ID[i])
{
nTimes2++;
}
else
{
if (nTimes3 == 0)
{
candidate3 = ID[i], nTimes3 = 1;
}
else
if (candidate3 == ID[i])
{
nTimes3++;
}
else
{
nTimes1--;
nTimes2--;
nTimes3--;
}
}
}
}
}
}
int main()
{
int a[] = {0,4,1,4,0,4,1,4,1,0,3,3,0,3,3,3};
Find(a,16);
cout << candidate1 <<" " << candidate2 << " " << candidate3 << endl;
return 0;
}