zoukankan      html  css  js  c++  java
  • 编程之美--寻找发帖水王(即:求数组主元素)

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

    分析与解法

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

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

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

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

    如 果每次删除两个不同的ID(不管是否包含“水王”的ID),那么,在剩下的ID列表中,“水王”ID出现的次数仍然超过总数

    (m/n>1/2;得2m>n

    (m-1)/n>1/(n-2) 得2m-2>n-2 的证。

    的一半。看到这一点之后,就可 以通过不断重复这个过程,把ID列表中的ID总数降低(转化为更小的问题),从而得到问题的答案。新的思路,避免了排序这个耗时的步骤,总的时间复杂度只 有O(N),且只需要常数的额外内存。伪代码如下:

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

    这个程序比较难写,我写的很久也没有写出来。

    candidate==id[i],相等加1,不相等减1,如果为0则candadate=id[i].

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

    扩展题:

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

    果每次删除四个不同的ID(不管是否包含发帖数目超过总数1/4的ID),那么在剩下的ID列表中,原先发帖比例大于1/4的ID所占比例仍然大于1/4。可以通过不断重复这个过程,把ID列表中的ID总数降低(转化为更小的问题),从而得到问题的答案。

    (注意是都超过了1/4

    m1/n>1/4

    (m1-1)/(n-2)/1/4

    void find4(int id[],int n,int candidate[3])
    {
        int id_NULL=INT_MAX;//定义一个不存在的id
        int nTimes[3];
        nTimes[0]=nTimes[1]=nTimes[2]=0;
        candidate[0]=candidate[1]=candidate[2]=id_NULL;
    
        for(int 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]--;
            }
        }
    }

    int a[]={5,1,1,1,1,2,2,2,2,3,3,3,3,6,7};
    int candidate[3];
    find4(a,sizeof(a)/sizeof(a[0]),candidate);
    for(int i=0;i<3;i++)
    cout<<candidate[i]<<ends;

    输出:3 1 2

    参考了:http://sxnuwhui.blog.163.com/blog/static/13706837320126325410824/

    求数组主元素的

    另一种思路:

    题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

    分析:

    如果一个数字才数组中出现的次数超过了数组长度的一半,那么对这个数组进行排序,位于数组中间位置的那个数就是出现次数超过一半的那个数。对数组排序的时间复杂度是O(nlog(n)),但是对于这道题目,还有更好的算法,能够在时间复杂度O(n)内求出。我们写过快速排序算法,其中的Partition()方法是一个最重要的方法,该方法返回一个index,能够保证index位置的数是已排序完成的,在index左边的数都比index所在的数小,在index右边的数都比index所在的数大。那么本题就可以利用这样的思路来解。

    1. 通过Partition()返回index,如果index==mid,那么就表明找到了数组的中位数;如果index<mid,表明中位数在[index+1,end]之间;如果index>mid,表明中位数在[start,index-1]之间。知道最后求得index==mid循环结束。
    2. 根据求得的index,遍历一遍数组,每当出现一个等于index所指向的数时time++,最后判断time是否大于数组长度的一半,如果大于则表明index所指向的数就是所求的数,如果不是,则表明不存在一个数出现的次数超过数组长度的一半。
    #include<iostream>
    #include<stdlib.h>
    using namespace std;
    
    //函数声明
    int MoreThanHalf(int arry[],int start,int end,int len);//函数入口
    int Partition(int arry[],int start,int end);//返回一个index,使index左边的数都比index所在的数小,index右边的数都比index所在数大
    bool CheckMoreThanHalf(int arry[],int len,int result);//判断一个数在数组中是否有超过一半
    
    int Partition(int arry[],int start,int end)
    {
        int pivotkey=arry[start];
        while(start<end)
        {
            while(start<end&&arry[end]>=pivotkey)
                end--;
            arry[start]=arry[end];
            while(start<end&&arry[start]<=pivotkey)
                start++;
            arry[end]=arry[start];
        }
        arry[start]=pivotkey;
        return start;
    }
    
    bool CheckMoreThanHalf(int arry[],int len,int result)
    {
        int time=0;
        for(int i=0;i<len;i++)
        {
            if(arry[i]==result)
                ++time;
        }
    
        bool isMoreThanHalf=true;
        if(time*2<=len)
            isMoreThanHalf=false;
        return isMoreThanHalf;
    }
    
    int MoreThanHalf(int arry[],int start,int end,int len)
    {
        if(arry==NULL&&len<=0)
            return -1;
    
        int index=Partition(arry,start,end);
        int middle=len/2;//中间位置
        while(index!=middle)
        {
            if(index>middle)//如果调整数组以后获得的index大于middle,则继续调整start到index-1区段的数组
                index=Partition(arry,start,index-1);
            else//否则调整index+1到end区段的数组
                index=Partition(arry,index+1,end);
        }
        //最后获取的index=middle,此时在middle左边的数小于arry[middle],在其右边的数大于arry[middle]
        int result=arry[middle];
        if(!CheckMoreThanHalf(arry,len,result))
            return -1;
    
        return arry[middle];
    }
    
    void main()
    {
        //int arry[]={5,1,7,3,0,2,8};//定义数组
        int arry[]={2,2,1,1,3};//定义数组
        int len=sizeof(arry)/sizeof(int);//求数组长度
    
        int half=MoreThanHalf(arry,0,len-1,len);
        cout<<half<<endl;
    
        system("pause");
    }
  • 相关阅读:
    预备作业03 20162311张之睿
    [LeetCode 题解]: String to Interger (atoi)
    [LeetCode 题解]: Add Two Numbers
    [LeetCode 题解]: Interger to Roman
    [LeetCode 题解]: Longest Substring Without Repeating Characters
    [LeetCode 题解]: Roman to Interger
    [LeetCode 题解]: palindromes
    [LeetCode 题解]: Two Sum
    [LeetCode 题解]: Maximum Subarray
    [LeetCode 题解]:Gas Station
  • 原文地址:https://www.cnblogs.com/youxin/p/3277689.html
Copyright © 2011-2022 走看看