昨天有个朋友面试遇上了这个算法,查阅了一下,颇感兴趣,记录一下。
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; }