【力扣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,需要两个候选人)

相关推荐
Shan12051 小时前
无向图的Hierholzer算法流程(一)
算法
一切皆是因缘际会1 小时前
频域特征解构底层机理与双域融合鉴伪算法优化
人工智能·算法·ai·架构
Smilecoc1 小时前
决策树(三):剪枝
算法·决策树·剪枝
bIo7lyA8v2 小时前
算法性能建模的数值方法与误差分析的技术8
算法
Smilecoc2 小时前
决策树(四):决策树实战之鸢尾花分类
算法·决策树·分类
-Thinker2 小时前
【无标题】
java·开发语言·算法·图搜索
数据仓库搬砖人2 小时前
DBSCAN 原理深度解析:从聚类算法到风控团伙识别的实战指南
算法
凡人叶枫2 小时前
Effective C++ 条款24:若所有参数皆须要类型转换,请为此采用 non-member 函数
linux·前端·c++·算法·嵌入式开发
洛水水2 小时前
【力扣100题】87.只出现一次的数字
数据结构·算法·leetcode