题目:随着Tango的发展,管理员发现,“超级水王”没有了。统计结果表明,有3个发帖很多的ID,他们的发帖数目都超过了帖子总数目N的1/4。你能从发帖ID列表中快速找出他们的ID吗?
分析:编美里面寻找水王的方法已经说明白了,遍历一遍,每次减少两个元素,将原问题分解成更小规模的问题,这其中的原因是因为减少了问题规模之后原来“水王帖子的数量超过总数的一半”的特性依然存在。扩展的问题思路也是一样,根据题意,水王们的发帖数目一定会超过1/4,因此在后面写代码的时候不考虑找出水王之后验证合不合法。参考原问题的解法,可以知道:如果每次删除四个不同的ID,则在剩下的ID列表中,原先发帖比例大于1/4的ID所占比例仍然大于1/4。不断重复这个过程,最终得到答案。
网上有很多直接在原问题的解法基础上,暴力遍历候选集candidates。在原问题的时候candidates只有一个,使用一个变量来保存就可以,在拓展问题中candidates有三个,因此需要用一个长度为3的数组来保存,另外用于计数candidates出现次数的标志也从原来的一个变量变成一个长度为3的数组。之所以说这种解法暴力,是大家的代码因为有很多if-else,把各种情况考虑周全了。这里转一份网上大神们的参考代码(转载地址,侵删。)
void Find(Type* ID, int N,Type candidate[3])
{
Type ID_NULL;//定义一个不存在的ID
int nTimes[3], i;
nTimes[0]=nTimes[1]=nTimes[2]=0;
candidate[0]=candidate[1]=candidate[2]=ID_NULL;
for(i = 0; i < N; i++)
{
if(ID[i]==candidate[0])
{
nTimes[0]++;
}
else if(ID[i]==candidate[1])
{
nTimes[1]++;
}
else if(ID[i]==candidate[2])
{
nTimes[2]++;
}
else if(nTimes[0]==0)
{
nTimes[0]=1;
candidate[0]=ID[i];
}
else if(nTimes[1]==0)
{
nTimes[1]=1;
candidate[1]=ID[i];
}
else if(nTimes[2]==0)
{
nTimes[2]=1;
candidate[2]=ID[i];
}
else
{
nTimes[0]--;
nTimes[1]--;
nTimes[2]--;
}
}
return;
}
但是,我寻思着,如果不止3个水王呢?如果是4个水王,5个水王,甚至是更多水王?事实上1个水王的时候水贴超过一半,3个水王的时候每人水贴超过1/4,可以推出有n个水王,每人所占水贴超过1/(n+1),因此应该有个通用的思路来找出这些水王。下面是我自己实现,遍历帖子的每一个元素时:1)首先检查candidates中有没有出现过,如果出现过,将对应的计数器加1;2.1)如果全部没有出现过,则找到第一个计数器为0的位置,作为这个candidate的的位置,并把计数器加1;2.2)如果这些candidates的计数器全部不为0,以为着当前元素跟这n个水王候选都不一样,因此删去这(n+1)个人。相应的代码如下,为了便于测试,假设原数组中都是整数,因此可以用负数来初始化候选人数组。
void findKnums(int data[], int size) {
if (data == NULL || size <= 0) return;
int counts[NUM] = { 0 };
int results[NUM] = { -1 };
for (int i = 0; i < size; ++i) {
bool isInK = false;
bool hasZeroCount = false;
for (int j = 0; j < NUM; ++j) {
if (results[j] == data[i]) {
counts[j]++;
isInK = true;
break;
}
}
if (!isInK) {
for (int j = 0; j < NUM; ++j) {
if (0 == counts[j]) {
results[j] = data[i];
counts[j]++;
hasZeroCount = true;
break;
}
}
if (!hasZeroCount) {
for (int j = 0; j < NUM; ++j)
counts[j]--;
}
}
}
for (int j = 0; j < NUM; ++j)
cout << results[j] << " ";
cout << endl;
}