news 2026/9/10 1:26:00

LeetCode-Go 题解:229. Majority Element II —— Boyer-Moore 多数投票算法求出现次数超过 n/3 的元素

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode-Go 题解:229. Majority Element II —— Boyer-Moore 多数投票算法求出现次数超过 n/3 的元素

LeetCode-Go 题解:229. Majority Element II —— Boyer-Moore 多数投票算法求出现次数超过 n/3 的元素

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

导读

本篇文章围绕 LeetCode 第 229 题 Majority Element II(求数组中出现次数超过 ⌊n/3⌋ 的所有元素)展开,以 0229 题解文档 为核心骨架,并结合仓库中 229 题 Go 实现 与 单元测试 进行源码级佐证。读完本文,你将掌握:为什么超过 ⌊n/3⌋ 的元素至多只有两个、如何把经典的 Boyer-Moore 多数投票算法从“找 1 个众数”扩展为“找 2 个候选者”,以及如何在 O(n) 时间、O(1) 空间内一次性筛出全部答案。

题目描述

Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times.

Note: The algorithm should run in linear time and in O(1) space.

题目大意:给定一个大小为 n 的整数数组,找出其中所有出现次数超过 ⌊ n/3 ⌋ 次的元素。算法要求时间复杂度为 O(n),空间复杂度为 O(1)。

示例 1:

Input: [3,2,3] Output: [3]

示例 2:

Input: [1,1,1,3,3,2,2,2] Output: [1,2]

解题思路

与 169 题的关系:从“1 个众数”到“2 个候选者”

本题是 169. Majority Element(找出现次数大于 ⌊n/2⌋ 的众数)的加强版,采用的算法是Boyer-Moore Majority Vote Algorithm(摩尔投票算法)的扩展版

先回顾 169 题的核心思想:数组中超过一半的元素,可以与所有其他元素“一对一抵消”后仍然存活。因此维护一个候选者和一个计数器,遍历数组时计数为 0 就换候选人,相同则 +1、不同则 -1,最终剩下的候选者即为众数。仓库中 169 题的 解法一实现 正是这一思想的直接体现:

// 解法一 时间复杂度 O(n) 空间复杂度 O(1) func majorityElement(nums []int) int { res, count := nums[0], 0 for i := 0; i < len(nums); i++ { if count == 0 { res, count = nums[i], 1 } else { if nums[i] == res { count++ } else { count-- } } } return res }

而 229 题把阈值从 ⌊n/2⌋ 降到 ⌊n/3⌋,问题结构发生了质变:

  • 超过 ⌊n/2⌋ 的元素至多存在 1 个,所以 169 题只需维护 1 个候选者;
  • 超过 ⌊n/3⌋ 的元素至多存在 2 个——因为若有 3 个元素都超过 n/3,它们出现次数之和将大于 n,与总和为 n 矛盾。源码注释精确地表达了这一推导:
// since we are checking if a num appears more than 1/3 of the time // it is only possible to have at most 2 nums (>1/3 + >1/3 = >2/3)

因此算法需要同时维护两个候选者 candidate1、candidate2 和两个计数器 count1、count2,这就是 Boyer-Moore 投票算法的扩展形式。

扩展投票:双候选者的三阶段流程

仓库中 229. Majority Element II.go 的解法一完整实现了该算法,整个流程分为三个阶段:

阶段一:选举候选者(Select Candidates)

遍历数组,对每个元素 num 依次判断:

  1. num == candidate1,则count1++
  2. 否则若num == candidate2,则count2++
  3. 否则若count1 <= 0,说明候选者 1 已“弹尽粮绝”,把candidate1替换为 num,重置count1 = 1
  4. 否则若count2 <= 0,同理替换候选者 2;
  5. 否则两个候选者“双双失血”,count1--count2--

这一阶段结束后,真正超过 ⌊n/3⌋ 的元素一定留在两个候选者之中(因为它的出现次数足以抵消所有其他元素而不被替换掉),但候选者并不一定是答案——由于计数可能被互相抵消,可能出现“没有候选者真正超过 ⌊n/3⌋”的情况,例如数组[1,2,3,4]

阶段二:重新计数(Recount)

count1count2清零,再次遍历数组,分别统计candidate1candidate2的真实出现次数。这一步是扩展版与 169 题的关键差异:169 题可假定众数必然存在,而本题不能,必须用真实计数做最终裁决。

阶段三:按阈值过滤输出

length := len(nums) if count1 > length/3 && count2 > length/3 { return []int{candidate1, candidate2} } if count1 > length/3 { return []int{candidate1} } if count2 > length/3 { return []int{candidate2} } return []int{}

分别判断两个候选者的计数是否严格大于length/3,按情况返回两个、一个或空切片。注意必须严格大于(>),恰好等于 ⌊n/3⌋ 不算答案。

初始值的一个易错细节

实现中初始化为count1, count2, candidate1, candidate2 := 0, 0, 0, 1,即两个候选者初始值不同(0 与 1)。原因在于:若两个候选者初始值相同,当数组第一个元素恰好等于该值时,两个分支会同时命中导致计数混乱。由于题目并未限定元素取值范围,采用两个不同的占位初值可以规避这一边界问题,也无需依赖 nil/哨兵值。

复杂度分析

  • 时间复杂度:O(n)。两次线性扫描(选举 + 重新计数),每次都是单层循环,无嵌套。
  • 空间复杂度:O(1)。只使用 4 个固定整型变量(两个候选者 + 两个计数器),不随输入规模增长。
  • 完全满足题目要求的 linear time 与 O(1) space。

另一种解法:哈希表计数(O(n) 空间)

如果题目没有 O(1) 空间约束,解法二 提供了更直观的哈希表方案:第一遍遍历用map[int]int统计每个元素出现次数,第二遍遍历 map,把计数大于len(nums)/3的键收集进结果切片:

// 解法二 时间复杂度 O(n) 空间复杂度 O(n) func majorityElement229_1(nums []int) []int { result, m := make([]int, 0), make(map[int]int) for _, val := range nums { if v, ok := m[val]; ok { m[val] = v + 1 } else { m[val] = 1 } } for k, v := range m { if v > len(nums)/3 { result = append(result, k) } } return result }

该写法在 169 题中同样有对应的 map 计数版实现。它时间上仍为 O(n),但空间升为 O(n),可作为理解题意与验证投票算法正确性的参照实现。

单元测试验证

仓库为该题提供了 229. Majority Element II_test.go,覆盖了 4 组典型用例,恰好对应上述所有边界分支:

输入预期输出覆盖场景
[3,2,3][3]恰好 1 个元素超过 n/3(2/3 次)
[1,1,1,3,3,2,2,2][1,2]同时存在 2 个元素超过 n/3
[1,2,3,4][]没有任何元素超过 n/3(返回空切片)
[2,1,1,1,3][1]1 出现 3 次 > 5/3,其余均不满足

测试驱动方式为表驱动测试(table-driven):定义question229结构体组合输入para229与期望答案ans229,遍历用例后同时调用majorityElement229majorityElement229_1两种实现,确保两套解法行为一致。其中[1,2,3,4]这一用例特别有价值——它专门验证了“候选者不一定为答案”的边界:投票阶段会留下两个候选者,但重新计数后发现二者都不达标,最终正确返回空切片。

小结

  • 超过 ⌊n/3⌋ 的元素至多两个,这是算法可行性的数学前提;
  • Boyer-Moore 投票算法扩展版通过维护双候选者 + 双计数器,在一次线性遍历中完成“候选人筛选”,再用第二次线性遍历做“真实计票”,最终在 O(n) 时间、O(1) 空间内找出全部答案;
  • 与 169 题的关键区别在于:169 题众数必然存在可直接返回候选者,而本题必须重新计数验证,因为候选者可能“虚高”;
  • 仓库提供了投票版与哈希表版两套实现,并由覆盖 4 种边界情形的表驱动测试保证正确性,可直接参考 实现源码 与 测试源码 深入研读。

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 1:23:22

GPT-6 Astra幻觉率2%却被老式SQL注入绕过?大模型安全防线为何失效

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 1:23:13

Qt滑动选择器自绘实践:从QSlider到产品级控件

简介&#xff1a;面向需要实现个性化滑动选择交互的Qt开发者&#xff0c;这份资源提供了一套完整可运行的自定义滑动选择器控件源码。控件支持水平/垂直模式自由切换、背景/滑块颜色定制、最小最大值与初始值设置&#xff0c;并在滑动时触发事件回调&#xff0c;适合音量调节、…

作者头像 李华
网站建设 2026/9/10 1:22:28

Unigram完全解析:Windows平台终极Telegram体验指南

Unigram完全解析&#xff1a;Windows平台终极Telegram体验指南 在众多跨平台即时通讯应用中&#xff0c;Telegram以其出色的安全性和丰富的功能赢得了全球用户的青睐。而在Windows平台上&#xff0c;Unigram作为原生的Telegram客户端&#xff0c;凭借其深度优化的系统集成和卓…

作者头像 李华
网站建设 2026/9/10 1:21:41

基于Unity的汽车零部件产线数字孪生方案设计与落地实践

做汽车零部件产线的数字孪生&#xff0c;最怕什么&#xff1f;最怕做出来一个好看但没用的“数字展厅”。产线数据接不进来、模型动不起来、设备状态对不上、产线一抖动就崩溃&#xff0c;这套东西就算画面再炫&#xff0c;车间主任也不会打开第二回。我过去大半年一直在折腾一…

作者头像 李华