🎤 开场白:这是一场没有主持人的选举
今天,我们来到 LeetCode 国家年度众数大选 的直播现场。
候选人名单已公布,选民(数组元素)正在入场。
但奇怪的是——
没有主持人,没有计票台,甚至连记事本都不准带!
只有一块黑板写着五个字:
“同归于尽”规则
而两位主角也已就位:
- 一位是西装笔挺的 哈希表先生,拎着公文包,准备一本本登记选票;
- 另一位是穿风衣戴墨镜的 摩尔投票法特工007,冷冷地说:“我不需要记录,我只要知道最后谁还站着。”
欢迎收看本期《算法界·权力的游戏》——
《众数选举大会》现场直击!
🏛️ 第一幕:简单多数制 —— LeetCode 169 大选直播
📌 规则说明
这就像总统大选:得票过半者直接上位,无需 runoff。
👔 候选人A:哈希表先生(传统派)
他一进场就摆出三件套:
- 一个 Map 表格
- 一支笔
- 一张“我要统计所有人”的决心脸
function majorityElement(nums) {
const map = new Map();
const threshold = Math.floor(nums.length / 2);
for (const num of nums) {
map.set(num, (map.get(num) || 0) + 1);
if (map.get(num) > threshold) return num;
}
}
📌 优点:严谨、可追溯、不易出错
📌 缺点:太老实了!空间 O(n),像背着账本打仗
💬 网友弹幕:“这不是在写代码,是在做审计啊……”
🕶️ 候选人B:摩尔投票法特工(激进派)
他走进来,把所有纸张烧掉,只留下一句话:
“真正的赢家,不会被淘汰。”
💡 核心战术:擂台赛 + 同归于尽
想象一场擂台战:
- 初始擂主为空,血量为 0
- 新选手上台:
- 是自己人 ➜ 加血 ❤️
- 是对手 ➜ 扣血 💔
- 血量归零 ➜ 换人守擂!
最终活下来的,就是那个占了半壁江山的“真命天子”。
function majorityElement(nums) {
let candidate = null;
let count = 0;
for (const num of nums) {
if (count === 0) candidate = num;
count += num === candidate ? 1 : -1;
}
return candidate;
}
📌 优点:O(1) 空间,极致优雅,宛如艺术
📌 缺点:太反直觉,新手看了直呼“这也能行?”
💬 弹幕爆炸:“原来淘汰别人的方式,是让他们互相消耗?!”
👉 这哪是算法,这是政治博弈!
🏛️ 第二幕:三分天下局 —— LeetCode 229 群雄争霸赛
📌 规则升级
这就不再是总统大选,而是议会席位争夺战!
数学定理告诉我们:
能拿下超过 1/3 选票的人,最多只有 2 个。
(再多就挤爆会场了 😂)
所以这次,我们需要两个“候选人位”。
🧮 哈希表先生再次登场:我依然是最稳的会计
他叹了口气:“又要加班了……”
但他依旧专业:
function majorityElement(nums) {
const countMap = new Map();
const threshold = Math.floor(nums.length / 3);
const result = [];
for (const num of nums) {
const count = (countMap.get(num) || 0) + 1;
countMap.set(num, count);
// 第一次达到 threshold+1 时才加入(防重复)
if (count === threshold + 1) {
result.push(num);
// 最多两个,提前下班
if (result.length === 2) break;
}
}
return result;
}
📌 评价:勤恳、可靠、值得托付财务大权
📌 吐槽:能不能别每次都从头算起?
🕴️ 摩尔投票法特工启动「双人宫斗模式」!
这一次,他带来了两位“代理人”:c1 和 c2。
他们立下军令状:
“我们将用最小代价,锁定潜在赢家。”
🎭 宫斗剧本正式开演
| 场景 | 剧情发展 |
|---|---|
| 某元素 == c1 | c1 得势,加一票 ✅ |
| 某元素 == c2 | c2 反击,也加一票 ✅ |
| 新势力来袭,且 c1 血量=0 | 新人上位,接管阵营!🔥 |
| 新势力来袭,且 c2 血量=0 | 再换人,继续战斗!🔥 |
| 都不匹配,且都有血量 | 二者各扣一票 ❌(三方混战,集体内耗) |
这就是传说中的 “抵消策略”:
不让任何一方轻易积累优势,直到真正有群众基础的候选人浮现。
⚠️ 但请注意:这只是初选!必须二次验证!
💡 经典翻车案例:[1,2,3]
- 投票结束后,c1=1, c2=2
- 看似两人入围决赛
- 实际上每人只得了1票,根本没超1/3红线!
🚨 所以必须开启第二轮:
回到原始数据,重新统计真实得票数!
// Step 2: 重新计票,确认是否真的“超三分之一”
cnt1 = 0; cnt2 = 0;
for (const num of nums) {
if (num === c1) cnt1++;
else if (num === c2) cnt2++;
}
const res = [];
if (cnt1 > nums.length / 3) res.push(c1);
if (cnt2 > nums.length / 3) res.push(c2);
📌 正如历史告诉我们的:
能走到决赛的人,未必是真正的王者。
🥇 终极对决:哈希表 vs 摩尔投票法
| 维度 | 哈希表先生 | 摩尔投票法特工 |
|---|---|---|
| 身份 | 注册会计师 | 特种作战专家 |
| 工具 | 笔记本 + Excel | 直觉 + 黑板 |
| 战术风格 | 稳扎稳打 | 快速筛选 |
| 是否需要复查 | 否(自带完整证据链) | 是(必须二次验证) |
| 空间复杂度 | O(n) | O(1) ✅ |
| 时间效率(JS) | 较慢(Map 开销大) | 极快(变量操作) |
| 面试表现 | “我会做” | “我懂本质” |
🎯 建议策略:
- 面试紧张想不起宫斗剧情?先写哈希表保底 ✔️
- 想让面试官眼前一亮?补一句:“其实还可以 O(1) 空间” ➜ 开始表演摩尔投票 💥
💣 血泪教训:那些年我们在选举中踩过的坑
❌ 坑1:阈值理解错误
“超过 n/3” ≠ “大于等于 n/3”
✅ 正确判断:count > Math.floor(n / 3)
🚫 错误写法:count >= n / 3
比如 n=5,⌊5/3⌋=1,要的是 ≥2 次才算。
❌ 坑2:忘记二次验证(宫斗剧结局被篡改)
很多同学以为“投完票就结束了”,直接宣布结果:
// 错!这是假新闻!
return [c1, c2];
记住:初选不等于当选,必须回炉重炼!
❌ 坑3:边界情况翻车
测试用例虽小,杀伤力极大:
[] → []
[1] → [1]
[1,1,2,2] → [1,2]
[1,2,3] → []
务必手动跑一遍,别让自己成为“选举事故责任人”。
🌟 思维跃迁:从“计数思维”到“博弈思维”
| 方法 | 思维方式 | 适用场景 |
|---|---|---|
| 哈希表 | 收集一切信息,再决策 | 数据量小、追求稳妥 |
| 摩尔投票法 | 边对抗边筛选,抓住关键少数 | 空间受限、追求极致 |
这不仅是算法选择,更是思维方式的进化:
有时候我们不需要知道每个人投了谁,
我们只需要知道——谁最后还站着。
📊 一张表看懂两道题的本质差异
| 项目 | LeetCode 169 | LeetCode 229 |
|---|---|---|
| 条件 | > n/2 | > n/3 |
| 最多答案数 | 1 | 2 |
| 核心算法 | 单人摩尔投票 | 双人摩尔投票 |
| 是否需要验证 | 可省略(题目保证存在) | 必须验证! |
| 空间复杂度 | O(1) | O(1) |
| 类比 | 总统大选 | 议会席位争夺 |
🏁 结语:算法如戏,全靠演技
这两道题,像是算法世界的“启蒙双子星”:
- 169 是入门券:让你见识 O(1) 空间的神奇;
- 229 是毕业考:教会你推广与验证的重要性。
刷题不止为了过面试,更是为了培养一种思维方式:
如何在混乱中抓住主线?
如何用最少资源锁定最大可能?
如何避免“表面胜利”的陷阱?
🌟 记住那句话:
“能 AC 的代码就是好代码,但优雅的 AC 更让人愉悦。”
📢 互动时间
你在“众数选举”中翻过车吗?
有没有被 [1,2,3] 背刺的经历?
欢迎在评论区分享你的“宫斗失败史”或“逆袭封神战”👇
🔥 如果你觉得这篇文章既有趣又有料
❤️ 请点赞 + ⭐ 收藏 + 🔄 分享,让更多人看到这场精彩的“算法大选”!
❤️ 愿你在刷题路上,既能运筹帷幄,也能笑对 bug。