当前位置: 首页 > news >正文

LeetCode 169 · 多数元素:Boyer-Moore 投票算法,最优雅的 O(1) 空间解法

这道题最有趣的地方不在于答案本身,而在于那个 O(1) 空间的解法——Boyer-Moore 投票算法。第一次看到的时候我愣了好一会儿:不需要哈希表、不需要排序、不需要分治,就两个变量,扫一遍就能找到出现超过一半的元素?是的,而且原理极其巧妙。


题目长什么样

给定一个大小为n的数组nums,返回其中的多数元素。多数元素是指在数组中出现次数大于⌊n/2⌋的元素。

输入nums = [2,2,1,1,1,2,2]
输出2

说人话就是:数组里一定有一个元素出现次数超过一半,把它找出来。题目保证这个元素一定存在,所以不需要处理"没有多数元素"的情况。

"超过一半"这个条件非常强——它意味着多数元素的数量比所有其他元素加起来还多。正是这个性质让 Boyer-Moore 算法成为可能。


第一反应:哈希表计数

最直接的想法——遍历一遍,用哈希表统计每个元素出现的次数,然后找最大的。

fromcollectionsimportCounterclassSolutionHash:defmajorityElement(self,nums:List[int])->int:counts=Counter(nums)returnmax(counts,key=counts.get)
维度说明
时间O(n)遍历一遍 + 查找最大值
空间O(n)最坏情况每个元素都不同

时间已经是最优了,但空间不行。题目进阶要求 O(1) 空间,哈希表显然不满足。


利用排序:中位数一定是多数元素

因为多数元素超过一半,所以排序后位于中间位置的元素一定是多数元素——不管多数元素怎么分布,它一定覆盖中点。

classSolutionSort:defmajorityElement(self,nums:List[int])->int:nums.sort()returnnums[len(nums)//2]
维度说明
时间O(n log n)排序的代价
空间O(1)原地排序

空间满足了,但时间退步了。能不能两全其美?


最优解:Boyer-Moore 投票算法

这是这道题的精髓。思路可以用一句话概括:不同阵营互相抵消,最后剩下的一定是多数元素

怎么理解"投票"

把数组想象成一场选举:

  • 每个数字代表一个"候选人"
  • 遍历数组就像逐个人投票
  • 我们维护一个"候选人"和一个"计数器"
  • 遇到同阵营的(相同的数字),计数器 +1
  • 遇到不同阵营的(不同的数字),计数器 -1
  • 计数器归零时,换一个新的候选人
classSolution:defmajorityElement(self,nums:List[int])->int:count=0candidate=Nonefornuminnums:ifcount==0:candidate=num count+=(1ifnum==candidateelse-1)returncandidate

为什么这一定能找到多数元素?

关键推理:

多数元素的数量 > n/2,其他所有元素加起来 < n/2。 即使最坏情况——所有非多数元素都和多数元素抵消: 抵消掉的多数元素数量 = 非多数元素总数 < n/2 剩余的多数元素数量 = 多数元素总数 - 抵消掉的数量 > n/2 - n/2 = 0 所以多数元素永远不会被完全抵消,最后剩下的一定是它。

跑一遍示例 2

nums = [2, 2, 1, 1, 1, 2, 2] i=0: num=2, count=0 → 候选人设为 2, count=1 i=1: num=2, 2=2 → 同阵营! count=2 i=2: num=1, 1≠2 → 不同阵营! count=1 i=3: num=1, 1≠2 → 不同阵营! count=0 ← 归零了! i=4: num=1, count=0 → 候选人换为 1, count=1 i=5: num=2, 2≠1 → 不同阵营! count=0 ← 又归零了! i=6: num=2, count=0 → 候选人换为 2, count=1 最终候选人 = 2 ✓

注意中间候选人从 2 变成了 1,但最后又变回了 2。这就是"抵消"的过程——虽然中间状态看起来不对,但最终结果一定是正确的,因为多数元素的数量优势保证它不会被完全消灭。

再跑一个更直观的例子

nums = [2, 2, 2, 2, 1, 1, 1] i=0: num=2, count=0 → 候选人=2, count=1 i=1: num=2, 同阵营 → count=2 i=2: num=2, 同阵营 → count=3 i=3: num=2, 同阵营 → count=4 i=4: num=1, 不同阵营 → count=3 i=5: num=1, 不同阵营 → count=2 i=6: num=1, 不同阵营 → count=1 最终候选人 = 2 ✓ (2 出现 4 次,1 出现 3 次)

这个例子更清楚——多数元素从头到尾都是候选人,其他元素只是不断消耗计数器,但始终没能把 count 减到 0。


三种解法放在一起看

解法时间空间一句话评价
哈希表计数O(n)O(n)最直观,面试保底
排序取中位数O(n log n)O(1)利用了排序后中点必是多数元素的性质
Boyer-Moore 投票O(n)O(1)最优解,两个变量扫一遍

这道题教会我什么

"超过一半"是一个极强的条件

多数元素的数量比其他所有元素加起来还多。这个"数量优势"让它能在任何形式的"对抗"中存活。Boyer-Moore 本质上就是在模拟这种对抗——多数元素和不属于它的元素互相消耗,多数元素永远不会被耗尽。

这个思路可以推广:如果某个东西的数量严格超过总量的一半,那它一定能经受住任何"成对抵消"

最好的算法往往不需要额外数据结构

哈希表是万能的,但它不是免费的——O(n) 的空间不是小事。Boyer-Moore 告诉我们,有时候问题本身的数学性质已经蕴含了答案,不需要"暴力"地记录所有信息。只需要抓住最核心的不变量(多数元素不会被完全抵消),两个变量就足够了。

代码短不代表思路简单

Boyer-Moore 的代码只有 6 行,但背后的数学推理一点都不简单。面试时如果被问到这道题,光写出代码不够——你得能解释"为什么 count 归零时可以放心换候选人"。如果解释不清楚,面试官会认为你是背的而不是理解的。


完整测试代码

fromtypingimportListclassSolution:defmajorityElement(self,nums:List[int])->int:count=0candidate=Nonefornuminnums:ifcount==0:candidate=num count+=(1ifnum==candidateelse-1)returncandidateclassSolutionSort:defmajorityElement(self,nums:List[int])->int:nums.sort()returnnums[len(nums)//2]classSolutionHash:defmajorityElement(self,nums:List[int])->int:fromcollectionsimportCounter counts=Counter(nums)returnmax(counts,key=counts.get)if__name__=="__main__":s=Solution()nums=[3,2,3]print(f"输入:{nums}, 输出:{s.majorityElement(nums)}")nums=[2,2,1,1,1,2,2]print(f"输入:{nums}, 输出:{s.majorityElement(nums)}")nums=[1]print(f"输入:{nums}, 输出:{s.majorityElement(nums)}")nums=[6,5,5]print(f"输入:{nums}, 输出:{s.majorityElement(nums)}")nums=[2,2,2,2,1,1,1]print(f"输入:{nums}, 输出:{s.majorityElement(nums)}")

相关题目推荐

  • LeetCode 229 · 求众数 II(超过⌊n/3⌋的元素,Boyer-Moore 的扩展版,维护两个候选人)
  • LeetCode 1150 · 检查一个数是否在数组中占绝大多数(有序数组中验证多数元素)
http://www.jsqmd.com/news/892677/

相关文章:

  • 蓝桥杯之Remember the A La Mode-从贪心策略到资源分配的边界探索(C++实现)
  • Bottles:Linux平台Windows应用兼容性管理的革命性解决方案
  • 提取矩阵所有元素
  • Unity TextMeshPro中文显示乱码终极解决方案
  • 留学生论文被 Turnitin 判 AIGC 过高?PaperXie 一键帮你把 “机器味” 改成 “人写感”
  • 从体素到路径:手把手用C++实现一个简化版的Recast导航网格生成器
  • 学术文献高效翻译利器:Zotero PDF2zh完全指南
  • 杭州小程序制作公司 TOP 榜 2026|数字化转型靠谱服务商 - 软件测评师
  • 新人转行大模型避坑指南|大模型算法工程师掏心窝子分享4大真相,避坑指南来了!
  • 大厂级AI服务对接实战(OpenAI/Anthropic/Claude全栈集成手册)
  • 机器学习与可解释AI如何揭示董事会性别多样性对碳排放的非线性影响
  • 10分钟快速测智商!五大免费专业微信测试平台合集 - 时讯资讯
  • 如何快速配置DeepL翻译插件:3步实现浏览器专业级翻译体验
  • ChatGPT学术研究应用全链路拆解,覆盖选题挖掘→假设生成→代码辅助→图表描述→投稿信撰写
  • Unity反向遮罩实战:用Stencil NotEqual实现UI局部穿透
  • 网上点餐系统(源码+毕设)
  • 留学生论文 AIGC 超标慌?Paperxie 英文 Turnitin 降 AIGC,帮你稳过检测
  • Unity图片导入报错File could not be read根因解析
  • 【AI Daily】AI日报 | 2026-05-26
  • 成都专业标书代写公司选择榜实体办公+四重审核+中标保障指南 - 资讯快报
  • 开源免费!这款 AI 语音工作室让 ElevenLabs 都感到压力
  • 美容SaaS平台冷启动难题破解(Lovable真实压测数据曝光:QPS 12,800下0.98%超时率)
  • 2026广州发明专利申请哪家靠谱?实质审查答辩、预审加急、授权兜底、年费运维服务商测评清单 - 资讯快报
  • Lovable能源看板响应延迟超800ms?,性能调优工程师现场抓包定位Redis缓存穿透根因
  • 如何让AI生成的文案更有“人味儿”?我试过的5个方法
  • 答辩 PPT 熬到凌晨三点?PaperXie 一键生成 + 万套模板,帮你把时间抢回来
  • Switch-Toolbox:5个高效技巧掌握任天堂游戏文件编辑神器
  • Taotoken的Token Plan套餐为个人开发者带来的成本体感变化
  • 2026 年 Ai 呼叫系统哪家靠谱:云蝠智能大众信赖 - 17329971652
  • Lovable翻译平台API网关设计:QPS从1.2万飙升至8.6万的关键11行代码优化实录