把169题真正吃透,是我一直认为检验算法基本功是否扎实最有效的方式之一。这道题就是LeetCode上大名鼎鼎的“多数元素”(Majority Element),题目描述只有一句话:给定一个大小为 n 的数组,返回其中的多数元素,多数元素指在数组中出现次数大于 ⌊n/2⌋ 的元素。很多人刷过它,也能背出摩尔投票的模板,但当你追问一句“为什么抵消完之后剩下的就一定是多数元素”,很多人就卡住了。这篇文章准备把169题彻底拆透——从最暴力的解法一路优化到最优解,把每种思路的复杂度、适用场景、代码模板、面试追问一层层剥开,适合正在准备算法面试的开发者,也适合想系统整理LeetCode知识点、建立解题思维框架的朋友。
1. 169题的题面设定:一个“超过一半”带来的数学优势
1.1 “大于 ⌊n/2⌋”到底意味着什么
多数元素定义里最关键的一个词是“大于”,不是“大于等于”。数组长度 n=7 时,⌊7/2⌋=3,元素出现3次不满足条件,必须至少出现4次;n=8 时,⌊8/2⌋=4,必须至少出现5次。这个“严格超过一半”的设定非常讲究,它是后面所有巧妙解法的根基。
如果阈值换成“恰好一半”,事情就变得非常棘手。比如 [1,2,1,2] 里没有出现次数等于2且唯一的元素吗?其实1和2都出现了2次,你会面临“选哪个”的歧义问题。可一旦要求“超过一半”,在任意数组中,满足条件的元素最多只有一个。这个唯一性直接让我们可以把问题从“找多个可能答案”缩小成“锁定唯一目标”。
从数学角度还能推出一个直观结论:把多数元素出现次数记为 k,k > n/2,那么 k - (n - k) > 0。换句话说,多数元素的数量减去所有非多数元素的数量总和,结果一定是个正数。这个“差额为正”才是摩尔投票算法成立的底层原因,也是排序法“取中间位必然命中”的数学依据。很多刷题笔记会直接抛结论,但我建议你自己拿张草稿纸把 n=5、n=6、n=7 三种情况画一画,把元素排成排,会发现多数元素无论怎么分布,都像一根承重墙一样“压”住了数组的中间位置。
1.2 “题目总是保证存在多数元素”这句话有多重要
LeetCode 169 的题干里有一句非常容易被忽略的前提:可以假设数组非空,并且给定的数组总是存在多数元素。这句话直接决定了很多解法的代码可以写得非常短。
以摩尔投票为例,如果题目不保证多数元素存在,那算法跑完后得到的 candidate 可能只是一个“伪多数元素”,必须再遍历一遍数组,统计 candidate 的真实出现次数,确认大于 n/2 后才能返回。但正因为 169 题给了这个强保证,代码里可以直接 return candidate,省掉验证环节。
很多人在面试里翻车,就是因为把别的高频题里“可能不存在多数元素”的条件混进了 169 的解答里,或者反过来在 229 题(多数元素 II)里忘记做第二遍验证。我的建议是:读题时第一件事就在草稿纸上圈出“总是存在”四个字,它直接决定你要不要多写一个 for 循环。后文第4章我会专门展开这个问题的边界处理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解法进化的完整链路:从暴力哈希到排序分治
2.1 暴力枚举:只作为思考起点,不写进代码
最直观的思路肯定是暴力枚举:外层循环选一个元素,内层循环统计它在整个数组里出现了多少次,第一次遇到计数大于 n/2 的元素直接返回。时间复杂度是 O(n²),因为 n 个元素各数一遍要 O(n),两层循环就是 O(n²);空间复杂度 O(1)。
python复制def majority_element_brutal(nums):
n = len(nums)
for i in range(n):
count = 0
for j in range(n):
if nums[j] == nums[i]:
count += 1
if count > n // 2:
return nums[i]
return -1
为什么不推荐?当 n 到 10^5 级别时,n² 意味着 10^10 次操作,在 LeetCode 上几乎稳定超时。但暴力法的价值在于帮我们确认问题定义:既然我们要找的是出现次数超过一半的元素,那核心工作就是“计数”。任何后续优化,本质上都是在思考如何减少“计数”的开销。
2.2 哈希表计数:最通用也最容易想到的解法
用哈希表(Python 里的 dict / Counter、Java 里的 HashMap)统计所有元素的出现次数,遍历完后再扫一遍哈希表,找到计数大于 n/2 的那个键。
python复制from collections import Counter
class Solution:
def majorityElement(self, nums: List[int]) -> int:
counter = Counter(nums)
n = len(nums)
for num, cnt in counter.items():
if cnt > n // 2:
return num
时间复杂度 O(n),空间复杂度 O(n)。
哈希表解法最大的优点是通用性极强。不管题目问的是“出现次数大于 n/2”“出现次数大于 n/3”,还是“出现次数最多的元素”,哈希表都能一套打天下,逻辑简单、不容易写错。缺点是需要 O(n) 的额外空间,以及两次遍历(一次统计、一次查找)。
在面试中,我通常建议把哈希表作为第一个说出来的解法,因为它是“下限高、风险低”的代表。然后顺着面试官“能不能省掉额外空间”的追问,自然过渡到排序法或者摩尔投票法。但要注意,哈希表虽然能解出 169,但它没有用到“超过一半”这个特殊条件,属
