【LeetCode Hot100 刷题日记 (97/100)】69. 多数元素 —— Boyer-Moore 投票算法、数组、双指针、原地操作🗳️

5 阅读4分钟

📌 题目链接:169. 多数元素 - 力扣(LeetCode)

🔍 难度:简单 | 🏷️ 标签:数组、双指针、原地操作

⏱️ 目标时间复杂度:O(n)

💾 空间复杂度:O(1)


🧠 题目分析

给定一个大小为 n 的整型数组 nums,要求找出多数元素(Majority Element) —— 即在数组中出现次数 严格大于 ⌊n/2⌋ 的那个元素。

题目保证:

  • 数组非空;
  • 多数元素一定存在。

💡 关键洞察
由于多数元素的出现次数 > n/2,意味着它在数量上“压倒性”地超过其他所有元素之和。这种特性使得我们可以设计出非常高效的算法,甚至无需额外空间!


🎯 核心算法及代码讲解:Boyer-Moore 投票算法 ✅

📘 算法背景

Boyer-Moore 投票算法(Boyer–Moore Majority Vote Algorithm)由 Robert S. Boyer 和 J Strother Moore 于 1981 年提出,专门用于在线性时间、常数空间内找出数组中的多数元素(前提是多数元素存在)。

面试高频考点!这是少数能在 O(1) 空间下解决“众数”问题的经典算法,常被用于考察候选人对“抵消思想”和“状态维护”的理解。

🧩 核心思想:“同归于尽” + “最后幸存者即为答案”

  • 将多数元素视为“正军”,其他元素视为“敌军”。
  • 每当遇到一个与当前候选者相同的元素,就“增兵”(count++);
  • 遇到不同元素,则“两败俱伤”(count--);
  • 当 count 归零,说明当前候选者已被“消灭”,换下一个元素作为新候选者。

由于多数元素数量 > n/2,最终剩下的候选者必然是多数元素

📜 算法步骤(C++ 行注释版)

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int candidate = -1;  // 初始化候选多数元素(任意值均可)
        int count = 0;       // 计数器,记录当前候选者的“净优势”

        for (int num : nums) {
            if (num == candidate)
                ++count;                 // 遇到相同元素,支持者+1
            else if (--count < 0) {      // 遇到不同元素,先抵消(count--),若归零或负
                candidate = num;         // 则更换候选者为当前元素
                count = 1;               // 新候选者初始支持数为1
            }
        }
        return candidate;  // 最终 candidate 即为多数元素
    }
};

🔍 注意else if (--count < 0) 是关键写法!
先执行 count = count - 1,再判断是否 < 0。若为真,说明当前候选者已无“净支持”,需更换。

❓ 为什么这个算法正确?

我们用数学归纳法 + 抵消模型来理解:

  • 假设多数元素为 M,出现 k > n/2 次;
  • 其他所有元素总和 < n/2
  • 在遍历过程中,每一对 (M, 非M) 可以相互抵消;
  • 由于 M 的数量 > 非M 总数,M 必然无法被完全抵消
  • 因此,最后剩下的候选者只能是 M

🎓 进阶思考:该算法能否用于找“出现超过 n/3 次”的元素?
→ 可以!但需要维护两个候选者(因为最多有两个元素满足 > n/3)。这就是 “扩展 Boyer-Moore”,见 LeetCode 229 题。


🧭 解题思路(分步拆解)

  1. 明确问题约束:多数元素存在且唯一,出现次数 > n/2。
  2. 排除暴力法:O(n²) 时间不可接受。
  3. 考虑哈希表:O(n) 时间 + O(n) 空间,可行但不满足进阶要求。
  4. 考虑排序:O(n log n) 时间,中位数即答案,但时间不够优。
  5. 想到“抵消”策略:利用多数元素的“数量优势”,设计 O(n) + O(1) 算法。
  6. 实现 Boyer-Moore 投票:维护候选者与计数器,一次遍历完成。

📊 算法分析

方法时间复杂度空间复杂度是否满足进阶要求
暴力枚举O(n²)O(1)
哈希表O(n)O(n)
排序O(n log n)O(log n)(递归栈)
随机化期望 O(n)O(1)✅(但有随机性)
分治O(n log n)O(log n)
Boyer-Moore 投票O(n)O(1)✅✅✅

Boyer-Moore 是唯一同时满足 O(n) 时间 + O(1) 空间的确定性算法!


💻 完整代码

C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int candidate = -1;
        int count = 0;
        for (int num : nums) {
            if (num == candidate)
                ++count;
            else if (--count < 0) {
                candidate = num;
                count = 1;
            }
        }
        return candidate;
    }
};

// 测试
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    Solution sol;
    vector<int> nums1 = {3, 2, 3};
    cout << sol.majorityElement(nums1) << "\n"; // 输出: 3

    vector<int> nums2 = {2, 2, 1, 1, 1, 2, 2};
    cout << sol.majorityElement(nums2) << "\n"; // 输出: 2

    return 0;
}

JavaScript

/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function(nums) {
    let candidate = -1;
    let count = 0;
    for (const num of nums) {
        if (num === candidate) {
            count++;
        } else if (--count < 0) {
            candidate = num;
            count = 1;
        }
    }
    return candidate;
};

// 测试
console.log(majorityElement([3, 2, 3])); // 3
console.log(majorityElement([2, 2, 1, 1, 1, 2, 2])); // 2

🌟 本期完结,下期见!🔥

👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!

💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!