【力扣100题】88.多数元素

一、题目描述

给定一个大小为 n 的数组 nums,返回其中的多数元素

多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的 ,并且给定的数组总是存在多数元素。

示例 1:

复制代码
输入: nums = [3,2,3]
输出: 3

示例 2:

复制代码
输入: nums = [2,2,1,1,1,2,2]
输出: 2

提示:

  • 1 <= n <= 5 * 10^4
  • -10^9 <= numsi <= 10^9
  • 输入保证数组中一定有一个多数元素

二、解题思路总览

方法 时间复杂度 空间复杂度 说明
Boyer-Moore 投票算法 O(n) O(1) 票数相消,多数元素必胜
排序后取中位数 O(n log n) O(1) 多数元素一定在中间
哈希表统计 O(n) O(n) 统计每个元素出现次数

投票算法是最优解,同时满足 O(n) 时间和 O(1) 空间。


三、方法一:Boyer-Moore 投票算法(推荐)

3.1 核心思想

多数元素出现次数 > n/2,意味着它比其他所有元素加起来都多。

票数相消:将数组中的元素两两配对消去,剩余的必然是多数元素。

具体做法:

  • 维护一个候选人 candidate 和票数 count
  • 遇到相同元素,票数 +1
  • 遇到不同元素,票数 -1
  • 当票数归零时,换当前元素为新的候选人

3.2 算法流程图

复制代码
数组: [2,2,1,1,1,2,2], n=7, 多数元素出现次数 > 3.5 即至少4次

众数 = 2(出现4次)或 1(出现3次,不满足条件)

+------------------------------------------------------------+
|  初始化: candidate = nums[0] = 2, count = 1                 |
+------------------------------------------------------------+

+------------------------------------------------------------+
|  i=1: nums[1]=2 与 candidate 相同                           |
|        count++ -> count=2                                  |
|        candidate = 2                                       |
+------------------------------------------------------------+
         2
    [2, 2, ...]   count=2

+------------------------------------------------------------+
|  i=2: nums[2]=1 与 candidate 不同                           |
|        count-- -> count=1                                  |
|        candidate = 2                                       |
+------------------------------------------------------------+
         2
    [2, 2, 1]     count=1

+------------------------------------------------------------+
|  i=3: nums[3]=1 与 candidate 不同                           |
|        count-- -> count=0                                  |
|        count归零,更换候选人: candidate = nums[3] = 1        |
|        count重置为1                                         |
+------------------------------------------------------------+
         1
    [2, 2, 1, 1]  count=1

+------------------------------------------------------------+
|  i=4: nums[4]=1 与 candidate 相同                           |
|        count++ -> count=2                                   |
|        candidate = 1                                        |
+------------------------------------------------------------+
         1
    [2, 2, 1, 1, 1]  count=2

+------------------------------------------------------------+
|  i=5: nums[5]=2 与 candidate 不同                           |
|        count-- -> count=1                                   |
|        candidate = 1                                        |
+------------------------------------------------------------+
         1
    [2, 2, 1, 1, 1, 2]  count=1

+------------------------------------------------------------+
|  i=6: nums[6]=2 与 candidate 不同                           |
|        count-- -> count=0                                  |
|        count归零,更换候选人: candidate = nums[6] = 2        |
|        count重置为1                                         |
+------------------------------------------------------------+
         2
    [2, 2, 1, 1, 1, 2, 2]  count=1

最终 candidate = 2,即为多数元素

相消的直观理解:

复制代码
数组: [2, 2, 1, 1, 1, 2, 2]

配对相消(相同元素配对,不同元素抵消):
  [2, 2]   -> 抵消
  [1, 1]   -> 抵消
  [1]      -> 剩下 1
  [2, 2]   -> 抵消

实际过程(票数法):
  票数: 2 -> 1 -> 0(换人) -> 1 -> 2 -> 1 -> 0(换人) -> 1
  候选人: 2 -> 2 -> 2 -> 1 -> 1 -> 1 -> 1 -> 2

最终候选人 = 2

3.3 完整代码(投票算法)

cpp 复制代码
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int candidate = nums[0];
        int count = 1;

        for (int i = 1; i < nums.size(); i++) {
            if (candidate == nums[i]) {
                count++;
            } else {
                count--;
            }

            if (count == 0) {
                candidate = nums[i];
                count = 1;
            }
        }

        return candidate;
    }
};

3.4 count 初始化为 1 的版本

cpp 复制代码
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        int count = 1;
        int ans = nums[0];

        for (int i = 1; i < n; i++) {
            if (ans != nums[i]) {
                count--;
            } else {
                count++;
            }

            if (count < 0) {
                count = 1;
                ans = nums[i];
            }
        }

        return ans;
    }
};

两种写法的区别:

版本 初始 count 触发换人的条件
标准版 count = 0 count == 0 时换人
count=1版 count = 1 count < 0 时换人

本质相同,只是初始化不同导致的边界处理差异。

3.5 复杂度分析

复杂度 说明
时间 O(n) 遍历数组一次
空间 O(1) 只用两个变量

四、方法二:排序后取中位数

4.1 核心思想

多数元素出现次数 > n/2,排序后它一定会占据数组的中间位置。

关键结论 :排序后下标为 n/2 的元素一定是多数元素。

4.2 算法流程图

复制代码
数组: [2,2,1,1,1,2,2]  ->  排序后: [1,1,1,2,2,2,2]

+------------------------------------------------------------+
|  数组长度 n = 7                                             |
|  中位数下标 = n/2 = 3                                        |
+------------------------------------------------------------+

排序后数组:
+---+---+---+---+---+---+---+
| 1 | 1 | 1 | 2 | 2 | 2 | 2 |
+---+---+---+---+---+---+---+
              ^
          下标 3

nums[3] = 2,即为多数元素

为什么是 n/2?

复制代码
多数元素出现次数 > n/2
例如 n=7,多数元素至少出现 4 次

排序后,多数元素占用的位置:
  - 如果集中在左边,至少占据下标 0,1,2,3
  - 如果集中在右边,至少占据下标 3,4,5,6
  - 如果分散分布,也至少占据下标 3

所以下标 n/2 处一定是多数元素

4.3 完整代码

cpp 复制代码
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        return nums[nums.size() / 2];
    }
};

4.4 复杂度分析

复杂度 说明
时间 O(n log n) 排序的时间复杂度
空间 O(1) 若原地排序

五、方法三:哈希表统计

5.1 核心思想

遍历数组,统计每个元素出现的次数,最后找出次数大于 n/2 的元素。

5.2 算法流程图

复制代码
数组: [2,2,1,1,1,2,2], n=7, 阈值 = n/2 = 3.5

+------------------------------------------------------------+
|  遍历数组,统计每个元素出现次数                               |
+------------------------------------------------------------+

  统计: 1 -> 3次
        2 -> 4次

+------------------------------------------------------------+
|  找出次数 > 3 的元素                                         |
+------------------------------------------------------------+

  count[2] = 4 > 3 -> 答案 = 2

结果: 2

5.3 完整代码

cpp 复制代码
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int, int> freq;
        int threshold = nums.size() / 2;

        for (int num : nums) {
            freq[num]++;
            if (freq[num] > threshold) {
                return num;
            }
        }

        return 0;  // 不会走到这里
    }
};

5.4 复杂度分析

复杂度 说明
时间 O(n) 遍历数组一次
空间 O(n) 哈希表存储所有数字

六、方法对比总结

方法 时间复杂度 空间复杂度 推荐指数
Boyer-Moore 投票算法 O(n) O(1) ★★★★★
排序后取中位数 O(n log n) O(1) ★★★☆☆
哈希表统计 O(n) O(n) ★★☆☆☆
复制代码
为什么投票算法是最优解?

1. 时间 O(n),满足要求
2. 空间 O(1),满足要求
3. 代码简洁
4. 体现数学思维(票数相消)

投票算法的正确性证明:
- 多数元素出现次数 > n/2
- 将数组分成两部分:多数元素部分和其他元素部分
- 多数元素数量 > 其他元素数量
- 两两配对消除后,剩余的一定是多数元素

七、面试追问 FAQ

问题 回答
Q: 投票算法的正确性如何保证? 多数元素出现次数 > n/2,每次不同元素相消,最多消除所有非多数元素,剩余的必是多数元素
Q: 投票算法为什么空间是 O(1)? 只用两个变量 candidatecount,不依赖额外数据结构
Q: 如果有多个出现次数 > n/3 的元素呢? 需要扩展投票算法,维护两个候选人和两个计数器(摩尔投票的扩展)
Q: 排序后取中位数的方法有什么局限? 需要修改原数组(排序),且时间复杂度是 O(n log n)
Q: 投票算法和哈希表方法哪个更快? 理论上相同 O(n),实际投票算法常数更小,且不需要额外空间
Q: 如何证明排序后下标 n/2 一定是多数元素? 反证法:假设不是,则多数元素出现次数 <= n/2,矛盾

八、相关题目

题目 难度 关键点
169. 多数元素 简单 Boyer-Moore 投票算法
229. 多数元素 II 中等 扩展投票算法(两个候选人)
面试题 17.10. 主要元素 简单 投票算法变形

九、总结

要点 说明
核心条件 出现次数 > n/2,必存在
投票算法 票数相消,最终候选人就是答案
排序法 多数元素必在中间位置
时间复杂度 O(n)
空间复杂度 O(1)
扩展思考 多数元素 II(出现次数 > n/3,需要两个候选人)

相关推荐
vibecoding日记2 小时前
双非如何快速入职字节等大厂大模型?真实案例分析:推理优化和投机解码
算法·求职·大模型工程师
yszaygr21384 小时前
Verilog参数化游程编码RLE模块
算法
望易4 小时前
刚设计的大模型架构-双域耦合认知框架
算法·架构
复杂网络8 小时前
多个 Claude Code 与多个 Codex 协同工作:设计与实现方案
算法
HjhIron1 天前
面试常客:字符串算法从入门到进阶
算法·面试
吴佳浩1 天前
DeepSeek DSpark:Confidence-Scheduled Speculative Decoding 技术解析
人工智能·算法·deepseek
触底反弹1 天前
🧠 搞懂 Token,才算真正入门大模型——从分词原理到 Embedding 语义实战
javascript·人工智能·算法
vivo互联网技术1 天前
ICLR 2026 | 基于后验采样的图像恢复方法LearnIR:人脸去阴影、去雾
人工智能·算法·aigc
浮生望1 天前
JS字符串与回文算法:从包装类到双指针的面试进阶之路
javascript·算法
黄敬峰1 天前
面试必刷:从JS底层包装类到双指针,彻底搞懂字符串与回文算法
算法