寻找发帖“水王”与扩展算法——《编程之美》

Tango是微软亚洲研究院的一个试验项目。研究院的员工和实习生们都很喜欢在Tango上面交流灌水。传说,Tango有一大“水王”,他不但喜 欢发贴,还会回复其他ID发的每个帖子。坊间风闻该“水王”发帖数目超过了帖子总数的一半。如果你有一个当前论坛上所有帖子(包括回帖)的列表,其中帖子 作者的ID也在表中,你能快速找出这个传说中的Tango水王吗?

分析与解法

首先想到的是一个最直接的方法,我们可以对所有ID进行排序。然后再扫描一遍排好序的ID列表,统计各个ID出现的次数。如果某个ID出现的次数超过总数的一半,那么就输出这个ID。这个算法的时间复杂度为ON * log2 N + N )。

如果ID列表已经是有序的,还需要扫描一遍整个列表来统计各个ID出现的次数吗?

如果一个ID出现的次数超过总数N 的一半。那么,无论水王的ID是什么,这个有序的ID列表中的第N /2项(从0开始编号)一定会是这个ID(读者可以试着证明一下)。省去重新扫描一遍列表,可以节省一点算法耗费的时间。如果能够迅速定位到列表的某一项(比如使用数组来存储列表),除去排序的时间复杂度,后处理需要的时间为O (1)。

但上面两种方法都需要先对ID列表进行排序,时间复杂度方面没有本质的改进。能否避免排序呢?

如果每次删除两个不同的ID(不管是否包含“水王”的ID),那么,在剩下的ID列表中,“水王”ID出现的次数仍然超过总数的一半。看到这一点之 后,就可以通过不断重复这个过程,把ID列表中的ID总数降低(转化为更小的问题),从而得到问题的答案。新的思路,避免了排序这个耗时的步骤,总的时间 复杂度只有ON ),且只需要常数的额外内存。伪代码如下:

代码清单2-8

Type Find(Type* ID, int N)
{
    Type 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;
}

在这个题目中,有一个计算机科学中很普遍的思想,就是如何把一个问题转化为规模较小的若干个问题。分治、递推和贪心等都是基于这样的思路。在转化过 程中,小的问题跟原问题本质上一致。这样,我们可以通过同样的方式将小问题转化为更小的问题。因此,转化过程是很重要的。像上面这个题目,我们保证了问题 的解在小问题中仍然具有与原问题相同的性质:水王的ID在ID列表中的次数超过一半。转化本身计算的效率越高,转化之后问题规模缩小得越快,则整体算法的 时间复杂度越低。

扩展问题

随着Tango的发展,管理员发现,“超级水王”没有了。统计结果表明,有3个发帖很多的ID,他们的发帖数目都超过了帖子总数目N 的1/4。你能从发帖ID列表中快速找出他们的ID吗?

 

算法

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--;
                            }
                        }
                    }
                }
            }
        }
    }
}

类似扩展问题:一个百度的面试题:

       有一个很大的log文件,里面记录的是用户搜索的关键字,现在的要求是,统计被搜索最频繁的前10个关键字

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值