当前位置: 首页 > 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.zskr.cn/news/1396133.html

相关文章:

  • 向量空间JBoltAI v4.4:ReAct推理链走向全透明
  • 泰勒展开工程实践:函数近似与局部线性化的实时优化
  • Paginated Report实战:打造打印就绪的合规级分页报表
  • 学术文献高效翻译利器:Zotero PDF2zh完全指南
  • 从CentOS 8.3到Sentaurus TCAD:一次棘手的安装历险与排错实录
  • Unity反向遮罩实战:用Stencil NotEqual实现UI局部穿透
  • 10分钟快速测智商!五大免费专业微信测试平台合集 - 时讯资讯
  • 现在不掌握AI Agent低代码开发,半年后将失去项目主导权:一线CTO紧急发布的48小时速成路径
  • 【AI Daily】AI日报 | 2026-05-26
  • Lovable平台权限体系崩溃实录:RBAC+ABAC混合模型落地的4个生死关卡及修复代码
  • 机器学习气候模型在均匀增暖基准测试中的表现与挑战
  • 成都专业标书代写公司选择榜实体办公+四重审核+中标保障指南 - 资讯快报
  • Switch-Toolbox:5个高效技巧掌握任天堂游戏文件编辑神器
  • 开源免费!这款 AI 语音工作室让 ElevenLabs 都感到压力
  • Unity动画师必备:用Aim和Look At Constraint快速实现角色眼神追踪与武器瞄准
  • 深度进化:AI告别野蛮生长,迈入价值落地新时代
  • Taotoken的Token Plan套餐为个人开发者带来的成本体感变化
  • Unity生存游戏底层逻辑:代谢引擎与环境交互约束系统
  • 人类的科技不断向前发展并带动经济的启示
  • 复盘】2026年5月26日(周二)
  • 2026 中国智慧文旅解决方案行业深度研究:湖南途记互联综合实力排名第一 - 资讯快报
  • 2026年10款降AIGC平台亲测:最高AI率100%直降至0.12%
  • 机器学习结合NB515窄带测光:高效区分M型矮星与红巨星
  • 机器学习增强RANS与降阶建模:高效高精度湍流参数化模拟
  • 2026年西湖边热门公寓_文鸿金座_值得选择 - 资讯快报
  • Qwen3.6-27B以7%参数量超越397B旗舰:MoE稀疏化路由机制与专家平衡损失函数深度解析
  • ViGEmBus终极指南:Windows游戏控制器虚拟化的完整解决方案
  • Linux搭建DHCP服务器全教程:原理+四步握手+固定IP绑定实操
  • Unity迁移到Godot:3天极限重构实战指南
  • RIR-Mega:五万房间脉冲响应数据集,赋能音频AI算法开发与评估