【算法】摩尔投票法

首先从一个leetcode问题开始:169. 多数元素

题目要求就是统计一个数组中超过半数的元素,题目已经确保输入的元素中必然有一个符合要求。

这个问题可以有很多解法,比如排序,使用map统计每一个元素的数量等,但是时间复杂度都超过O(n)。摩尔投票法的出现能使得时间复杂度将为O(n)。

摩尔投票法(Boyer-Moore major vote algorithm)的具象理解

我们已知一个数组中有一个元素的数量超过了其他元素数量的总和(大于一半),但是不知道是哪一个元素。那么我们可以这样想,把数组看成一个投票箱,下标 i 即为 num[i] 投出了支持票,我们的目标很明确,我们不关心每个参与竞选的候选人的得票数,我们只需要知道是哪一个候选人的得票数超过了一半,我们可以假定候选人 i=0 支持的 nums[0] 是候选人,那么他需要满足他的得票比其他任何人都多,我们可以这样操作,我们统计他的得票count。我们顺序扫描每个人投出的票,如果是支持他的,则将count 加 1,否则就将 count 减一,当count数量降为0时,就意味着没人支持他了,我们就将候选人换成当前投票者i支持的候选人nums[i], 由于已知有一个支持者的得票数超过了一半,那么无论剩下的人支持谁,都不能撼动他到最后的。想象一下最恶劣的情况,假设这个候选人出现的数组的头部,那么他的得票数因为反对者都在他后面(1,2,3,…),即使时这样,他的支持者仍然能确保成为最后仅存的候选人,那么无论这个候选人出现在什么位置,依照上述规则投票,最后的候选人仍然是他,不可能会是其他候选人,因为其他候选人都会被这个最后的候选人的支持者反对出去(因为他们的数量足够)。

class Solution {
    public int majorityElement(int[] nums) {
        int count = 0;
        int candidate = nums[0];
        for (int num : nums) {
            if (count == 0) {
                candidate = num;
            }
            count += (num == candidate) ? 1 : -1;
        }

        return candidate;
    }
}

当然,上述算法求超过一半的数有个前提,就是输入的数据中一定要满足有一个元素的数量要超过一般,否则就无法得出上述结论。如果没有做出上述保证,我们可以在最后对候选人的得票数做一个统计,如果超过一半,则是说明存在一个数量超过一半的元素,并且就是当前元素,否则,就不存在这样的元素。

摩尔投票法就是用来解决求众数问题的

229. 求众数 II

这个问题和上面的问题有所不同,他要求两个这样的数字,但是思路是一样的。

class Solution {
    public List<Integer> majorityElement(int[] nums) {
        int cand1 = nums[0];
        int count1 = 0;
        int cand2 = nums[0];
        int count2 = 0;
        List<Integer> ans = new ArrayList<>();
        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) ans.add(cand1);
        if (count2 > nums.length / 3) ans.add(cand2);
        return ans;
    }
}

从这个问题推广出来,如果要选择出得票数超过1/(m+1)的元素,那么这样的元素最多有m个。在循环结束后需要判段这m个候选者是否得到了超过1/(m+1)的票数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值