算法小记 | Boyer-Moore 投票算法

15 阅读2分钟

Boyer-Moore 投票算法 (Boyer-Moore Voting Algorithm) 是解决“众数问题”的最优解

它的核心思想非常简单粗暴,可以类比为: “极限一换一”

假设有一个数组,定义超过一半的为众数,如何求这个众数?对应:169. 多数元素

核心逻辑:诸侯争霸

想象一下,这个数组是一个战场,里面有很多不同的国家(数字),每个数字代表一个士兵。

  • 众数(Majority Element):在这个战场上,某个国家的士兵人数 超过了总人数的一半(> n/2)。

  • 规则

    1. 士兵们在战场上通过,如果你遇到一个友军(相同的数字),你们的战斗力就增强(count + 1)。
    2. 如果你遇到一个敌人(不同的数字),不管他是哪个国家的,你们两个就同归于尽count - 1)。

推论: 因为众数的人数超过了一半,所以哪怕所有非众数的士兵联合起来,哪怕他们采用“自杀式袭击”和众数士兵一换一,最后活在战场上的那个(candidate) ,一定还是众数。


算法执行流程

我们需要维护两个变量:

  1. candidate (候选人) :当前擂台上的霸主。
  2. count (票数/血量) :霸主当前的势力值。

步骤:

遍历数组:

  1. 如果 count 为 0:当前没有霸主(或者之前的霸主被耗死了),那么当前的数字自动登基成为新的 candidate,并将 count 设为 1。

  2. 如果 count 不为 0:

    • 遇到友军 (num === candidate):count ++
    • 遇到敌军 (num !== candidate):count --(一换一消耗掉)。

图解演示

假设数组是:[2, 2, 1, 1, 1, 2, 2] (众数显然是 2)

步骤当前数字动作/逻辑Candidate (候选人)Count (血量)
Start-初始化null0
12count是0,2登基21
22是友军(2),血量+122
31是敌军(1),一换一,血量-121
41是敌军(1),一换一,血量-120 (2下台了!)
51count是0,1登基11
62是敌军(2),一换一,血量-110 (1下台了!)
72count是0,2登基21

结束:最终剩下的 candidate2


代码实现 (JavaScript)

/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function(nums) {
    let candidate = null;
    let count = 0;

    for (let num of nums) {
        // 1. 如果当前没有霸主(count为0),当前数字登基
        if (count === 0) {
            candidate = num;
        }

        // 2. 这里的逻辑简写了:
        // 如果是友军,count加1;如果是敌军,count减1
        count += (num === candidate) ? 1 : -1;
    }

    return candidate;
};

为什么它比 Map 好?

  • HashMap 方法:需要建立一个对象来存每个数字出现的次数。如果数组有 100 万个不同的数字,你就需要存 100 万个键值对。

    • 空间复杂度O(n)O(n)
  • Boyer-Moore 投票法:只需要 candidatecount 两个变量,不管数组多大,占用的内存都是固定的。

    • 空间复杂度O(1)O(1)
    • 时间复杂度O(n)O(n)