编程之美---寻找发帖“水王”

问题描述:

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

问题求解:

分析:在这个题目中,有一个计算机科学中很普遍的思想,就是如何把一个问题转化为规模较小的若干个问题。分治、递推和贪心等都有这样的思想。在转化过程中,如果能保证小的问题跟原问题的解是一致的就成功了。这样,我们可以通过寻找这样的方式将小问题转化为更小的问题。如何将大问题拆成小问题,或者如何大规模的数据降成小规模,而不影响解呢?

如果每次删除两个不同的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。
步骤如下:
i=0,candidate=0,nTimes=1;
i=1,a[1]=1 != candidate,nTimes–,=0;
i=2,candidate=2,nTimes=1;
i=3,a[3] != candidate,nTimes–,=0;
i=4,candidate=1,nTimes=1;
如果是0,1,2,1,1,1的话,那么i=5,a[5]=1=candidate,nTimes++,=2;……

#include <iostream>
using namespace std;
typedef int Type;

Type Find(Type* ID, int N)
{//ID代表数组,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;
}
int main()
{
    Type a[] = {2, 3, 4, 5, 3, 6, 3, 3, 4, 3, 6, 3};
    cout<<"The candidate is : "<<Find(a, 12)<<endl;
    return 0;
}

执行结果:

The candidate is : 3

能够利用该解法的关键在于“水王”发帖数目超过了帖子总数的一半,但是当刚好是一半的时候便不适用。此时可以做如下改进:

#include <iostream>
using namespace std;
typedef int Type;

Type Find(Type* ID, int N)
{//ID代表数组,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--;
            }
        }
    }
    //处理不存在出现次数超过一半的情况
    int cnt=0;
    for(int j=0;j<N;j++)
    {
        if(ID[j] == candidate) cnt++;
    }
    if(cnt > N/2) return candidate;
    else return 0;
}
int main()
{
    //Type a[] = {2, 3, 4, 5, 3, 6, 3, 3, 4, 3, 6, 3};
    Type a[] = {2, 3, 4, 3};
    cout<<"The candidate is : "<<Find(a, 4)<<endl;
    return 0;
}
    原文作者:爱橙子的OK绷
    原文地址: https://blog.csdn.net/will130/article/details/46005287
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞