ALGORITHM CLASS / 练习题 05
超过一半,
如何一眼认出?
数组中的多数元素
从两两抵消,到摩尔投票法
5 / 9 个位置,都属于数字 2
01 / 读懂题意
目标是“超过一半”,而不仅是“最多”
给定长度为 n 的数组,找到出现次数
严格大于 n / 2 的那个数字。
数字 2 出现 5 次,5 > 9 / 2,所以输出 2
题目已保证
数组非空,且一定有解
1 ≤ n ≤ 50,000
0 ≤ val ≤ 10,000
02 / 选择方法
我们真正需要记住多少信息?
多数元素比其余所有元素加起来还多,这个条件值得利用。
| 方法 | 时间复杂度 | 额外空间 | 核心做法 |
|---|---|---|---|
| 逐个统计 | O(n²) | O(1) | 每个元素都再扫描一遍 |
| 先排序 | O(n log n) | 取决于排序实现 | 取排序后的中间元素 |
| 哈希计数 | 平均 O(n) | O(k) | 记录 k 个不同值的频次 |
| 摩尔投票法 | O(n) | O(1) | 只维护候选值和剩余票数 |
从“统计每个人”转向 “抵消不同的人”
03 / 抵消的直觉
每次划掉两个不同的数字
多数元素比其余元素的总和还多,所以一定不会被全部抵消。
04 / 两个变量
把抵消过程,压缩成一个计数器
candidate当前保留下来的数字
也就是“候选值”
count该候选值在配对抵消后
还剩多少票
count = 1count++count--05 / LIVE DEMO
这一轮,谁留下?还剩几票?
点击“下一步”读取一个数字,观察候选值、票数与对应代码。
演示支持 1–18 个整数(0–10,000),用逗号或空格分隔。
06 / 正确性
抵消一对,多数身份依然成立
设多数元素出现 m 次,当前长度为 n,已知 m > n / 2。
拿走“多数元素 + 其他元素”
总长度减 2,多数元素的数量减 1。
两边同时减 1,严格大于依旧成立。
拿走“两个不同的其他元素”
总长度减 2,多数元素数量不变。
它在剩余数组中的比例反而更高。
反复抵消不同元素后,剩下的只会是多数元素。
candidate 和 count,正好记录这份“未抵消的余量”。
07 / 代码实现
一次遍历,两个变量
#include <vector>
int majorityElement(const std::vector<int>& nums) {
int candidate = 0, count = 0;
for (int x : nums) {
if (count == 0) {
candidate = x; count = 1;
} else if (x == candidate) {
++count;
} else {
--count;
}
}
return candidate; // 题目保证有解
}时间复杂度
额外空间复杂度
初始 candidate 可以为 0。
首次循环中,count == 0
会先把它更新为当前数字。
08 / 适用边界
有候选值,不代表一定有多数元素
反例:去掉“保证有解”的前提
count = 1
但 3 只出现 1 次,
1 ≤ 3 / 2,它并未过半。
也不能直接证明多数元素存在。
int frequency = 0;
for (int x : nums) {
if (x == candidate) ++frequency;
}
bool exists = frequency > nums.size() / 2;验证后仍是 O(n) 时间、O(1) 额外空间。
若允许空数组,还需要先处理空输入。
09 / 一起检查
这三个问题,你能解释吗?
先预测,再点开答案。
[2, 2, 1, 1, 1, 2, 2] 的最终状态?
count 减到 0 时,立刻拿当前元素重新投一票?
[1, 1, 2, 2] 是否有符合条件的数字?
10 / 回顾
多数元素,经得起不断抵消
不同就抵消,
相同就累加,
归零后,下一轮重选。
candidate 保存候选值count 保存未抵消的票数