数据结构 - 摩尔投票法(Boyer-Moore majority vote algorithm)
对应练习题目:
初级:https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/
进阶:https://leetcode-cn.com/problems/majority-element-ii/
1. 摩尔投票法
摩尔投票算法也可以叫做多数投票算法,本文全部使用Java代码。时间复杂度为O(n),空间复杂度为O(1)。
核心就是对拼消耗。玩一个诸侯争霸的游戏,假设你方人口超过总人口一半以上,并且能保证每个人口出去干仗都能一对一同归于尽。最后还有人活下来的国家就是胜利。
那就大混战呗,最差所有人都联合起来对付你(对应你每次选择作为计数器的数都是众数),或者其他国家也会相互攻击(会选择其他数作为计数器的数),但是只要你们不要内斗,最后肯定你赢。最后能剩下的必定是自己人。
1. 实例一
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
分析:
- res的票+1,非众数-1,根据题目的描述则一定有所有数字的票数和 > 0 ;
- 若数组的前 a 个数字的票数和 == 0 ,则数组剩余 (n-a) 个数字的票数和一定仍 > 0 ,即后 (n-a)个数字的最多数仍为 res。(严格来说是超过一半的数)
class Solution {
public int majorityElement(int[] nums) {
int res = 0, votes = 0, count = 0;
for(int num : nums){
if(votes == 0) res = num;//votes为0时 对res重新赋值
votes += num == res ? 1 : -1;
}
// 验证 res 是否为众数
for(int num : nums)
if(num == res) count++;
return count > nums.length / 2 ? x : 0; // 当无众数时返回 0
}
}
每次从数组中找出一对不同的元素,将它们从数组中删除,直到遍历完整个数组。由于这道题已经说明一定存在一个出现次数超过一半的元素,所以遍历完数组后数组中一定会存在至少一个元素。
2. 实例二
给定一个大小为 n 的整数数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。
**分析:**使用摩尔投票法,一次移动以三个数组为单位,所以到最后一定是会留下超过n/3的元素,且注意这里的元素一定是只有两个(三个大于n/3的数之和一定大于n,不符)!(这里就可以衍生到超过1/n次的元素的做法)。
public class Solution{
public List<Integer> majorityElement(int[] nums) {
int len = nums.length;
List<Integer> list = new ArrayList<>(2);//初始化容量
int count1 = 0,count2 = 0,count3 = 0,votes1 = 0,votes2 = 0,votes3 = 0,res1 = 0,res2 = 0,res3 = 0;//一次删除三个数 大于n/3的数只能是两个
for(int num:nums){
if(votes1 == 0) res1 = num;
if(votes2 == 0) res2 = num;
if(votes3 == 0) res3 = num;
if(res1 == num) votes1++;
else if(res2 == num) votes2++;//else 很有关键 它使得num1和num2能够分开
else if(res3 == num) votes3++;
else{
votes1--;
votes2--;
votes3--;
}
}
//验证
for (int num:nums) {
if(res1 == num) count1++;
if(res2 == num) count2++;
if(res3 == num) count3++;
}
if(count1 > len/3) list.add(res1);
if(res1 != res2 && count2 > len/3) list.add(res2);
if(res1 != res3 && res2 != res3 && count3 > len/3) list.add(res3);
return list;
}
}
3. 题外话
在LeetCode上面还有另外一种被推崇的解析,我认为它的continue容易误导人对数据结构在大脑中构建,看代码:
class Solution {
public List<Integer> majorityElement(int[] nums) {
List<Integer> ans = new ArrayList<>();
int aimLen = nums.length / 3;
int res1 = 0, res2 = 0, count1 = 0, count2 = 0;
for (int cur : nums) {
if (cur == res1) {
++count1;
continue;
}
if (cur == res2){
++count2;
continue;
}
if (count1 == 0){
res1 = cur;
count1 = 1;
continue;
}
if (count2 == 0){
res2 = cur;
count2 = 1;
continue;
}
--count1;
--count2;
}
count1 = 0;
count2 = 0;
for (int cur : nums){
if (cur == res1) {
++count1;
continue;
}
if (cur == res2) ++count2;
}
if (count1 > aimLen) ans.add(res1);
if (count2 > aimLen) ans.add(res2);
return ans;
}
}
一次是判断三个数,上面的使用continue缺没有真正的做到一次判断三个数。具体解析看官网把…