• 摩尔投票


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

    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;
        }
  • 相关阅读:
    深度系统安装wine
    Android应用真正的入口在哪里?
    推荐系统算法概览
    Android面试题:Scrollview内嵌一个Button他的事件消费是怎样的?Move,Down,Up分别被哪个组件消费?
    Java面试题:多线程交替打印字符串
    EventBus使用初体验
    Andoird面试题:A活动启动B活动,他们的生命周期各是怎么样的?
    Android在开发过程中如何选择compileSdkVersion,minSdkVersion和targetSdkVersion
    华为2020暑期实习面经(已拿Offer)
    工商银行软件开发中心2020暑期实习面经(已拿Offer)
  • 原文地址:https://www.cnblogs.com/shoulinniao/p/14636929.html
Copyright © 2020-2023  润新知