算法讲堂超过一半的数字

ALGORITHM CLASS / 练习题 05

超过一半,
如何一眼认出?

数组中的多数元素
从两两抵消,到摩尔投票法

O(n) 时间O(1) 额外空间
123222542

5 / 9 个位置,都属于数字 2

找到那个 比其他所有数字加起来还多 的数字课堂互动 · 约 10 分钟

01 / 读懂题意

目标是“超过一半”,而不仅是“最多”

给定长度为 n 的数组,找到出现次数
严格大于 n / 2 的那个数字。

题目示例 · n = 9
123222542

数字 2 出现 5 次,5 > 9 / 2,所以输出 2

O(n)时间复杂度
O(1)额外空间复杂度

题目已保证

数组非空,且一定有解

1 ≤ n ≤ 50,000
0 ≤ val ≤ 10,000

长度为 9 时,至少出现 5 次 才满足条件BM51 · 摩尔投票法

02 / 选择方法

我们真正需要记住多少信息?

多数元素比其余所有元素加起来还多,这个条件值得利用。

方法时间复杂度额外空间核心做法
逐个统计O(n²)O(1)每个元素都再扫描一遍
先排序O(n log n)取决于排序实现取排序后的中间元素
哈希计数平均 O(n)O(k)记录 k 个不同值的频次
摩尔投票法O(n)O(1)只维护候选值和剩余票数

从“统计每个人”转向 “抵消不同的人”

Boyer–Moore Majority Vote · 本题的目标解法BM51 · 摩尔投票法

03 / 抵消的直觉

每次划掉两个不同的数字

多数元素比其余元素的总和还多,所以一定不会被全部抵消。

123222542
先选出一对不同的数字:1 和 2
当前剩余的数字 2 / 剩余元素
5 / 9 > 1 / 2
数组中其他数字可以不同,但它们都在消耗多数元素的“对手票”BM51 · 摩尔投票法

04 / 两个变量

把抵消过程,压缩成一个计数器

candidate

当前保留下来的数字
也就是“候选值”

count

该候选值在配对抵消后
还剩多少票

01
没有剩余票:count == 0把当前数字 x 选为新候选值
count = 1
02
x 与 candidate 相同多来了一张支持票
count++
03
x 与 candidate 不同与一张支持票配对抵消
count--
count 是抵消后的剩余票数,不是候选值在原数组中的出现次数。
count 归零后,在 下一轮读取新数字时 才重新选择候选值BM51 · 摩尔投票法

05 / LIVE DEMO

这一轮,谁留下?还剩几票?

点击“下一步”读取一个数字,观察候选值、票数与对应代码。

演示支持 1–18 个整数(0–10,000),用逗号或空格分隔。

candidate / 候选值—
count / 剩余票数0
还没有读取数字。先让 count = 0。
if (count == 0) {
candidate = x; count = 1;
} else if (x == candidate) {
++count;
} else { --count; }
// count 不是实际频次
0 / 9 步
原题保证有解;本演示结束后额外统计频次,帮助核对结果。可输入新数组 · 随时回退

06 / 正确性

抵消一对,多数身份依然成立

设多数元素出现 m 次,当前长度为 n,已知 m > n / 2。

情况 A

拿走“多数元素 + 其他元素”

m − 1 > (n − 2) / 2

总长度减 2,多数元素的数量减 1。
两边同时减 1,严格大于依旧成立。

情况 B

拿走“两个不同的其他元素”

m > n / 2 > (n − 2) / 2

总长度减 2,多数元素数量不变。
它在剩余数组中的比例反而更高。

反复抵消不同元素后,剩下的只会是多数元素。
candidate 和 count,正好记录这份“未抵消的余量”。

因为多数元素始终严格过半,所以不会在抵消中全部消失BM51 · 摩尔投票法

07 / 代码实现

一次遍历,两个变量

C++ · 输入通过 const 引用传入
#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; // 题目保证有解
}
每个元素处理一次O(n)

时间复杂度

candidate 与 countO(1)

额外空间复杂度

初始 candidate 可以为 0。
首次循环中,count == 0
会先把它更新为当前数字。

适用于 非空且保证存在多数元素 的输入BM51 · 摩尔投票法

08 / 适用边界

有候选值,不代表一定有多数元素

反例:去掉“保证有解”的前提

123→
candidate = 3
count = 1

但 3 只出现 1 次,
1 ≤ 3 / 2,它并未过半。

即使最终 count > 0,
也不能直接证明多数元素存在。
通用版本 · 再扫描一次验证
int frequency = 0;
for (int x : nums) {
    if (x == candidate) ++frequency;
}
bool exists = frequency > nums.size() / 2;

验证后仍是 O(n) 时间、O(1) 额外空间。
若允许空数组,还需要先处理空输入。

本题保证有解,因此 可以省略验证;改变前提,就要补回验证BM51 · 摩尔投票法

09 / 一起检查

这三个问题,你能解释吗?

先预测,再点开答案。

01

[2, 2, 1, 1, 1, 2, 2] 的最终状态?

candidate = 2,count = 1;实际出现次数为 4。票数轨迹:1 → 2 → 1 → 0 → 1 → 0 → 1。
02

count 减到 0 时,立刻拿当前元素重新投一票?

不可以。当前元素已经参与抵消;要在读取下一个元素时,重新选择候选值。
03

[1, 1, 2, 2] 是否有符合条件的数字?

没有。每个数字只出现 2 次,等于长度的一半;题目要求严格超过一半。
能讲清 为什么抵消有效,才算真正掌握了这个算法BM51 · 摩尔投票法

10 / 回顾

多数元素,经得起不断抵消

不同就抵消,
相同就累加,
归零后,下一轮重选。

candidate 保存候选值
count 保存未抵消的票数
> ½多数元素比其他元素加起来还多
题目来源:用户提供的练习题 5 · 牛客 BM51Boyer–Moore Majority Vote