“众数选举大会”现场实录:哈希表当会计,摩尔投票法搞宫斗

79 阅读6分钟

🎤 开场白:这是一场没有主持人的选举

今天,我们来到 LeetCode 国家年度众数大选 的直播现场。
候选人名单已公布,选民(数组元素)正在入场。

但奇怪的是——
没有主持人,没有计票台,甚至连记事本都不准带!

只有一块黑板写着五个字:

“同归于尽”规则

而两位主角也已就位:

  • 一位是西装笔挺的 哈希表先生,拎着公文包,准备一本本登记选票;
  • 另一位是穿风衣戴墨镜的 摩尔投票法特工007,冷冷地说:“我不需要记录,我只要知道最后谁还站着。”

欢迎收看本期《算法界·权力的游戏》——
《众数选举大会》现场直击!


🏛️ 第一幕:简单多数制 —— LeetCode 169 大选直播

📌 规则说明

image.png

这就像总统大选:得票过半者直接上位,无需 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 群雄争霸赛

📌 规则升级

image.png

这就不再是总统大选,而是议会席位争夺战!

数学定理告诉我们:

能拿下超过 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;
}

📌 评价:勤恳、可靠、值得托付财务大权
📌 吐槽:能不能别每次都从头算起?


🕴️ 摩尔投票法特工启动「双人宫斗模式」!

这一次,他带来了两位“代理人”:c1c2

他们立下军令状:

“我们将用最小代价,锁定潜在赢家。”

🎭 宫斗剧本正式开演

场景剧情发展
某元素 == c1c1 得势,加一票 ✅
某元素 == c2c2 反击,也加一票 ✅
新势力来袭,且 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 169LeetCode 229
条件> n/2> n/3
最多答案数12
核心算法单人摩尔投票双人摩尔投票
是否需要验证可省略(题目保证存在)必须验证!
空间复杂度O(1)O(1)
类比总统大选议会席位争夺

🏁 结语:算法如戏,全靠演技

这两道题,像是算法世界的“启蒙双子星”:

  • 169 是入门券:让你见识 O(1) 空间的神奇;
  • 229 是毕业考:教会你推广与验证的重要性。

刷题不止为了过面试,更是为了培养一种思维方式:
如何在混乱中抓住主线?
如何用最少资源锁定最大可能?
如何避免“表面胜利”的陷阱?

🌟 记住那句话:
“能 AC 的代码就是好代码,但优雅的 AC 更让人愉悦。”


📢 互动时间

你在“众数选举”中翻过车吗?
有没有被 [1,2,3] 背刺的经历?
欢迎在评论区分享你的“宫斗失败史”或“逆袭封神战”👇

🔥 如果你觉得这篇文章既有趣又有料

❤️ 请点赞 + ⭐ 收藏 + 🔄 分享,让更多人看到这场精彩的“算法大选”!


❤️ 愿你在刷题路上,既能运筹帷幄,也能笑对 bug。