摩尔投票

昨天有个朋友面试遇上了这个算法,查阅了一下,颇感兴趣,记录一下。

1.概念

摩尔投票指的是在众多投票中选出得票最多的候选人,众数。

常见的例子就是分布式系统,例如redis主从复制中,主服务器客观下线,监视主服务器的哨兵Sentinel需要选出一个领头羊对下线服务器进行故障转移,得票过半才能选上。

2.思想

常规思想是对候选人的得票计数,选最大的,时空复杂度都是O(n)。

摩尔投票算法则只需要O(1)的空间复杂度,O(n)的时间复杂度,以得票过半为例,总票数为n。

  • 把候选人之间的计票操作 用 抵消票数 替代;
  • 用两个变量维持这个关系,候选人(res) 和 票数(cnt);
  • 遍历投票数组a过程中,如果得票者(a[i])和当前候选人(res)相同,则候选人票数+1;
  • 如果得票者(a[i])和当前候选人(res)不同,则当前候选人票数-1(cnt--),若减至0则更换候选人(res=a[i]),并设置票数为1(cnt=1);
  • 得票最多的候选人必然超过 [n/2],其他候选人的票数之和 必然少于 [n/2],相互抵消后最终留下来的候选人必然是得票最多者;

3.例题

(1)力扣169.多数元素

题意:找到出现次数大于一半的元素,摩尔投票裸题,题目必然出现次数大于一半的元素。

    /**169. 多数元素
     * 摩尔投票,找到超过1/2得票的候选人
     * 遇到相同的候选人,则票数+1,否则票数-1
     */
    public int majorityElement(int[] a) {
        int res=a[0],cnt=1;
        for(int i=1;i<a.length;i++){
            if(a[i]==res)
                cnt++;
            else
                cnt--;
            if(cnt==0){
                res=a[i];
                cnt=1;
            }  
        }
        return res;
    }

 

(2)力扣229. 求众数 II

题意:找到所有出现次数超过n/3的元素

思路:相比出现次数超过一半的,这里可以有2个结果,则需要两个候选人进行票数抵消;

对当前遍历到的票数a[i],与两个候选人进行比较;

如果有相同者,则得票的候选人票数+1,其他什么都不用管;

如果没有相同者,有候选人票数为0则更换候选人并设置票数为1,否则两个候选人票数都要-1;

最后判断票数最多的两人 票数是否超过[n/3];

同理可以类比票数超过[n/4],[n/5]...[n/i],或者等于[n/i]之类的题目;

    /**229. 求众数 II
     * 摩尔投票进阶版,找到票数超过 [n/3] 的所有候选人
     * 显然,符合条件的候选人不会超过2个
     * 找出得票最多的2个候选人 + 判断候选人票数是否>[n/3]
     */
    public List<Integer> majorityElement(int[] a) {
        List<Integer> res=new LinkedList<>();
        int n=a.length;
        int m1=a[0],cnt1=0;
        int m2=a[0],cnt2=0;
        //找出候选人,票数抵消阶段
        for(int i=0;i<n;i++){
            if( m1==a[i] ){
                cnt1++;
                continue;
            }
            if( m2==a[i] ){
                cnt2++;
                continue;
            }

            //更换候选人
            if(cnt1==0){
                m1=a[i];
                cnt1=1;
                continue;
            }
            if(cnt2==0){
                m2=a[i];
                cnt2=1;
                continue;
            }
            cnt1--;
            cnt2--;
        }
        //计数阶段
        cnt1=cnt2=0;
        for(int i=0;i<n;i++){
            if(a[i]==m1)
                cnt1++;
            else if(a[i]==m2)//如果m1=m2则计数都在cnt1,不用担心最后累加的时候会重复
                cnt2++;
        }
        if(cnt1>(n/3))
            res.add(m1);
        if(cnt2>(n/3))//如果m1=m2,则cnt2=0,不会重复
            res.add(m2);

        return res;
    }

 

4.谈一下个人的其他想法,这是我昨天被朋友问到后的第一想法,知道了空间复杂度为0(1)的情况下

如果限定候选人<104的话,利用int为109这个量级,可以在原数组上计数,前四位表示票数,后四位表示原本的投票者,也没有消耗多余的空间,96669527 表示9527候选人得到9666票。

    /**
     * 摩尔投票
     * 约定0<=n,a[i]<10000
     */
    public static int majorityElement(int[] a) {
        int n=a.length,x,cnt;
        for(int i=0;i<n;i++){
            x=a[i]%10000;//x 是真正的得票人
            cnt=a[x]/10000;//cnt 当前是x的得票数
            a[x]=(cnt+1)*10000+x;
        }

        x=0;
        cnt=a[0]/10000;
        for(int i=1;i<n;i++){
            if( a[i]/10000 > cnt ){
                cnt=a[i]/10000;
                x=i;
            }
        }
        return x;
    }

 

posted @ 2021-04-09 14:22  守林鸟  阅读(253)  评论(0编辑  收藏  举报