摩尔投票算法(Boyer-Moore Voting Algorithm)

写在前面

觉得写得好,有所收获,记得点个关注和点个赞,不胜感激。
今天遇到了一个新的计算众数的算法,觉得特别有用而且有趣,非常奇妙。然后又拜读了算法原作者的论文,觉得强无敌。这里贴出原作者的论文。引用原论文中的摘要开做本篇博文的开题(自己理解翻译原论文,不准确不要喷我,毕竟我还在考雅思的路上,嘿嘿嘿)。

给定一个数组A,其中含有 n n n 个元素,在数组A中出现超过 n / 2 n/2 n/2 次的元素,也就是出现次数超过 n / 2 n/2 n/2 的众数。现在的问题要求找到数组中是否存在这样的元素,其出现次数超过 n / 2 n/2 n/2。现在本文就来讨论在线性复杂度下,计算成本最优的算法。

理解摩尔投票算法

我们来思考上面的那个问题,你会发现其实如果不考虑时间复杂度或者空间复杂度的话,我们可以通过暴力计数的方式,统计数组中出现的元素的重复次数。这种方法很直接,很暴力,不过想要成为变牛逼,就不能只会大多数人都知道的暴力算法。所以Boyer-Moore Voting Algorithm也就出来了。让我们来理解它吧。

其实算法的思路不难,我们可以把算法理解成两两对抗,如果下一个数不同,那么就对当前数 − 1 -1 1,这么说可能很晦涩。我们用图来理解,在看下面的图解之前,我们先确定一件事儿,就是如果数组中存在这么一个数,它的出现次数大于 n / 2 n/2 n/2 ,那么它是唯一的,这个我就不多解释,你细品就知道了。

首先我们用两个变量,分别为 c u r cur cur 以及 c o u n t count count,记录当前元素以及当前元素尚存(过程中会被抵消)的次数。两个变量分别初始化为 n u m s [ 0 ] nums[0] nums[0] 以及 0 0 0,如下。
在这里插入图片描述
然后我们遍历数组,首先遇到第一个元素A,发现相同,那么 c u r cur cur 不变, c o u n t count count 计数加一

在这里插入图片描述
接着继续移动,遇到元素B 发现元素不相同,那么相互抵消,所以 c u r cur cur 不变, c o u n t count count 计数减一
在这里插入图片描述
这个时候,继续移动到下一个元素,发现还是不同,而且当前的 c o u n t count count 计数为 0 0 0,那么这个时候将当前元素 c u r cur cur 赋值为移动的元素 C ,并且 c o u n t count count 计数加一。
在这里插入图片描述
按照上面的思路继续移动,一直到最结尾的过程如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一直到最后,发现当前变量 c u r cur cur c o u n t count count 分别是A和 3 3 3,这个时候算法还没有结束。因为我们仔细想一想,最后的 c o u n t count count 不为零,就能说明当前的元素符合要求,及出现的次数超过了 n / 2 n/2 n/2 么?当然不行。

遍历结束之后,当 c o u n t count count 不为零时,只能说明 c u r cur cur 是目前数组中出现次数最多的一个元素,也就意味着它有可能是我们要找的符合的要求的元素,它如果不可能,那么其他元素也就不可能。

那么我们知道结论之后,如何判断他的次数是否符合要求呢?很简单,再遍历一次数组就可以了,通过对该元素进行计数,进行判断。这样子,我们是算法的时间复杂度将会是 O ( 2 N ) O(2N) O(2N),也就是符合线性时间复杂度,而且空间复杂度为 O ( 1 ) O(1) O(1)

出现次数要求是 n/3或者n/k怎么办?

搞懂了上面的 n / 2 n/2 n/2 ,这些问题还会难么?不就是一个拓展的问题么?我们只需要明确一点,如果要求是 n / 3 n/3 n/3 ,那么数组中符合要求的元素最多只有两个, n / k n/k n/k 也是同样,最多只有 k − 1 k - 1 k1 个。明确了这个,我们顶多就是多预设几个变量的问题。以下用 n / 3 n/3 n/3 为例,进行图解。

同样的,同时设置四个变量,两个用来记录元素,两个用来记录元素出现的次数,并且记录元素的变量同时付赋初值为第一个元素,记录次数的两个变量统计赋初值为0。
在这里插入图片描述
通过第一次移动,发现元素相同,因为是按顺序先比对第一组元素计数(如果比对成功就直接进行下一个循环),所以第一次元素计数加一
在这里插入图片描述
接着移动,发现下一个元素B不相同,那么检查当前两组值中,谁的计数为空,然后复制为B,计数加一。
在这里插入图片描述
继续移动到下一个元素B,检查是否已经存在于两组元素中,如果存在,在对应的那组数中,计数加一
在这里插入图片描述
继续移动,发现下一个元素C,在两组数中都找不到对应的元素,那么,两组数计数同时减一。
在这里插入图片描述
移动到下一个元素B,那么B的那组数计数加一
在这里插入图片描述
移动到下一个元素C,发现不存在与两组数中,两组数应该同时减一,不过由于元素A的那组数的计数已经为零了,所以,替换为C,B的那组数计数正常减一。
在这里插入图片描述
按照如山的规则,一直遍历到最后的一个元素即可。过程如下。
在这里插入图片描述
在这里插入图片描述
一直到最后,发现两组数中的计数都不为零,所以可以知道,元素A和元素B都有可能是我们要找的符合要求的元素,所以这个时候,我们还是老样子遍历数组进行技术就可以了。一直扩展到k,也是同样的思路,就是多定义几个变量的事情。

例题代码

在这里插入图片描述

public int majorityElement(int[] nums) {
    int solider = nums[0];
    int count=0;
    for(int i=0;i<nums.length;i++){
        if(nums[i]==solider){
            count++;
        }else{
            if(count==0){
                solider=nums[i];
                count++;
            }else {
                count--;
            }  
        }
    }
    return solider;   
}

在这里插入图片描述

public List<Integer> majorityElement(int[] nums) {
    List<Integer> res = new ArrayList<>();
    if (nums == null || nums.length == 0) return res;
    int cand1 = nums[0], count1 = 0;
    int cand2 = nums[0], count2 = 0;

    for (int num : nums) {
        if (cand1 == num) {
            count1++;
            continue;
        }
        if (cand2 == num) {
            count2++;
            continue;
        }
        if (count1 == 0) {
            cand1 = num;
            count1++;
            continue;
        }
        if (count2 == 0) {
            cand2 = num;
            count2++;
            continue;
        }
        count1--;
        count2--;
    }

    count1 = 0;
    count2 = 0;
    for (int num : nums) {
        if (cand1 == num) count1++;
        else if (cand2 == num) count2++;
    }

    if (count1 > nums.length / 3) res.add(cand1);
    if (count2 > nums.length / 3) res.add(cand2);

    return res;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值