多数元素 II 图解题解
这道题到底在问什么
- nums
- [1,1,1,3,3,2,2,2]
- n/3
- ⌊8/3⌋ = 2 (要出现超过 2 次)
- 输出
- [1, 2] (1 出现 3 次、2 出现 3 次)
最优解:一步一步想明白
- 3假设有 3 个数都超过 n/3,那它们次数之和 > 3 × n/3 = n,比整个数组还长,不可能。所以答案至多 2 个——这是用「两个候选」的底气。
- 4169 题只用一个候选找过半的数;这里要找过 1/3 的,所以备两个候选 cand1/cand2,各带一个票数。规则只多了一条:当前数和两个候选都不同、且都还有票时,两边各减一票。
- 5cand1/cand2 都空,票数都 0开局两个候选都为空、票数都是 0。下面让一个指针从左往右逐个扫描,每扫一个就按规则更新这四个变量,盯住它们怎么变。
- 6cand1=1,票1=1第 1 个数 1。两个候选都还空着,先看 cand1 的票数是 0 → 让 1 占住 cand1,记 1 票。
- 7cand1=1,票1=2又一个 1,和 cand1 相同,给它加一票,票1 变 2。
- 8cand1=1,票1=3再一个 1,cand1 票数涨到 3。前三个数都是 1,cand1 攒了厚厚一沓票。
- 9cand2=3,票2=1来了个 3,和 cand1(=1) 不同。这时看 cand2 的票数是 0 → 让 3 占住 cand2,记 1 票。注意:没去减 cand1 的票,因为还有空候选位可占。
- 10cand2=3,票2=2又一个 3,和 cand2 相同,票2 加到 2。现在 cand1=1(3 票)、cand2=3(2 票),势均力敌。
- 11票1=2,票2=1关键一步:来了个 2,和两个候选(1、3)都不同,而且两边都还有票。规则规定:两个票数各减一(2 自己也没站住)。候选不换,只是两边各掉一票。
- 12票1=1,票2=0再一个 2,还是都不同,两票再各减一。票2 被减到 0——cand2(=3) 的位子空出来了。
- 13cand2=2,票2=1最后一个 2。cand1 还是 1(还有 1 票),但 cand2 的位子空着 → 2 趁机占住 cand2,记 1 票。扫描结束:候选定格为 cand1=1、cand2=2。
- 14候选 = {1, 2},但还不能直接交卷第一遍只「选出」了两个嫌疑人 1 和 2。但摩尔投票的票数是抵消后的余票,不是真实出现次数——万一数组里根本没有过 1/3 的数,也会硬选出两个。所以必须再扫一遍,数它们的真实次数。
- 15起步 · v1 = 0第二遍开始,先验证 cand1=1。准备一个真实计数 v1=0,从头逐个看是不是 1。
- 16v1 = 1第一格是 1,和 cand1 相同,真实计数 v1 加到 1。接着往后数。
- 17v1 = 3数下来,1 一共出现 3 次(前三格),后面都不是 1。v1=3。
- 181 真的过 1/3,收进答案判定:v1=3,3 > 2 成立 → 1 确实是过 1/3 的数,收进答案。
- 19起步 · v2 = 0同样地验证 cand2=2。重置真实计数 v2=0,再从头数是不是 2。
- 20v2 = 32 出现在最后三格,一共 3 次。v2=3。
- 212 也过 1/3,收进答案v2=3,3 > 2 也成立 → 2 入选。两个候选都通过验证,最终答案 [1, 2],收工!
- 22n=3,⌊3/3⌋=1换个小例子 [3,2,3] 走一遍,重点看第一遍会选出两个候选,但只有一个能通过验证——这就是第二遍存在的意义。
- 23cand1=3票1=2,cand2=2票2=13 占 cand1;2 看到 cand2 空着也占了 cand2;最后的 3 让票1 涨到 2。第一遍硬选出两个候选 {3, 2}——可 2 其实只出现 1 次。
- 243 入选、2 被淘汰第二遍真实计数:3 出现 2 次 > 1 入选;2 只出现 1 次,不过 1/3,被淘汰。最终 [3]。看,不验证就会把假候选 2 也交上去——这就是第二遍的价值。
⚠️ 容易写错的地方
✗ 错:选完候选直接返回,不做第二遍验证
✓ 对:必须第二遍数真实次数,>⌊n/3⌋ 才入选
票数是抵消余票不是真实次数;像 [3,2,3] 会硬选出假候选 2
✗ 错:把「占空位」和「双减」的判断顺序写反
✓ 对:先判「等于某候选」,再判「有空位(cnt==0)」,最后才双减
顺序错会在还能占空位时就去减票,candidate 选错
✗ 错:两个候选可能相等,验证时重复计入
✓ 对:初始化或验证时排除 cand1==cand2 的重复
全相同数组里 cand1、cand2 可能指向同一个值,避免答案出现重复元素
完整代码(Python / C++ / Java)
Python
class Solution:
def majorityElement(self, nums):
cand1 = cand2 = None
cnt1 = cnt2 = 0
for x in nums:
if cand1 is not None and x == cand1:
cnt1 += 1
elif cand2 is not None and x == cand2:
cnt2 += 1
elif cnt1 == 0:
cand1, cnt1 = x, 1
elif cnt2 == 0:
cand2, cnt2 = x, 1
else:
cnt1 -= 1; cnt2 -= 1
res = []
for c in (cand1, cand2):
if c is not None and nums.count(c) > len(nums) // 3:
res.append(c)
return resC++
class Solution {
public:
vector<int> majorityElement(vector<int>& nums) {
int cand1 = 0, cand2 = 0, cnt1 = 0, cnt2 = 0;
for (int x : nums) {
if (cnt1 > 0 && x == cand1) cnt1++;
else if (cnt2 > 0 && x == cand2) cnt2++;
else if (cnt1 == 0) { cand1 = x; cnt1 = 1; }
else if (cnt2 == 0) { cand2 = x; cnt2 = 1; }
else { cnt1--; cnt2--; }
}
int n = nums.size();
int v1 = 0, v2 = 0;
for (int x : nums) { if (x == cand1) v1++; else if (x == cand2) v2++; }
vector<int> res;
if (v1 > n / 3) res.push_back(cand1);
if (cand2 != cand1 && v2 > n / 3) res.push_back(cand2);
return res;
}
};Java
class Solution {
public List<Integer> majorityElement(int[] nums) {
int cand1 = 0, cand2 = 0, cnt1 = 0, cnt2 = 0;
for (int x : nums) {
if (cnt1 > 0 && x == cand1) cnt1++;
else if (cnt2 > 0 && x == cand2) cnt2++;
else if (cnt1 == 0) { cand1 = x; cnt1 = 1; }
else if (cnt2 == 0) { cand2 = x; cnt2 = 1; }
else { cnt1--; cnt2--; }
}
int n = nums.length, v1 = 0, v2 = 0;
for (int x : nums) { if (x == cand1) v1++; else if (x == cand2) v2++; }
List<Integer> res = new ArrayList<>();
if (v1 > n / 3) res.add(cand1);
if (cand2 != cand1 && v2 > n / 3) res.add(cand2);
return res;
}
}复杂度
时间复杂度
O(n)
第一遍扫一次选候选、第二遍扫一次数真实次数,共两遍线性扫描 → O(n)
空间复杂度
O(1)
只用 cand1/cand2/cnt1/cnt2/v1/v2 这几个固定变量,与 n 无关 → O(1)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 多数元素 II 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么是两个候选,不是一个或三个?+
过 ⌊n/3⌋ 的数最多 2 个(3 个则次数和超 n)。169 题找过半的数(最多 1 个)用 1 个候选;推广到 1/k 就用 k−1 个候选。
为什么第一遍选出候选后还要第二遍?+
摩尔投票的票数是抵消后的余票,不是真实次数;它只保证「若存在过 1/3 的数,一定在候选里」,但候选未必真过 1/3。所以要第二遍数真实次数确认。
推广到「出现超过 ⌊n/k⌋」怎么做?+
维护 k−1 个候选 + 计数,同样规则:同则加、占空位、都不同则全体减一;最后第二遍验证。时间 O(nk)、空间 O(k)。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 多数元素 II 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。