1. 刷了这么多年,为什么我仍推荐先做这题
先别急着把这道题归类为“无脑简单题”。它在 LeetCode 上的题号是 136,英文叫 Single Number,中文叫“只出现一次的数字”。题干只有一句话:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次,找出那个只出现了一次的元素。看起来是个入门级统计题,但它在面试里的出场率,一点都不比很多 hard 题低。我见过不少候选人,前 20 秒就抛出了哈希表版本,代码也对,但被追问一句“能不能不用额外空间”之后,就停住了。这个停顿,恰恰是 136 题真正的考点。
为什么会卡住?因为这道题的 Follow-up 写得很直白:用线性时间复杂度和常数空间解决。哈希表虽然能在线性时间内找到答案,却背着 O(n) 的额外空间。很多人在“能过样例”之后就满足了,根本没往 Follow-up 想。今天我想把这道题掰开揉碎:从哈希和排序解法讲起,再拆解它真正的钥匙——位运算里的异或,最后顺着 136 去看 260、137 这两道同门变形题,把“异或能解决什么问题、不能解决什么问题”一次讲清楚。无论你是刚开始刷题的初学者,还是准备面试想查漏补缺的开发者,这题都值得花一顿饭的时间彻底想透。
1.1 题目本身很简单,但大部分人的第一版答案并不达标
这道题卡人的地方不在读题,而在“优化”两个字。很多人一看数组、一看找唯一值,第一反应就是哈希表计数。这个反应本身没有错,在工程上,HashMap 几乎是解决“找唯一”问题的最通用方案。但算法题和工程需求的差别就在这里:工程里空间通常不是第一约束,算法题却经常要求在“不能开额外数组”的前提下完成任务。
如果你在 LeetCode 上只选了“通过”就结束,那你很可能会错过这道题最有价值的部分。题目给的限制条件“每个元素均出现两次”不是废话,它是一条极强的数学线索。你要在头脑里形成一个习惯:每个条件都是出题人留给你的钥匙。删掉这个条件,题目会立刻变成另一道题;保留这个条件,你就有机会把复杂度从 O(n) 空间压到 O(1) 空间。
1.2 它是一系列位运算题的“母题”
LeetCode 热门 100 题里,真正需要靠异或解决的题目其实就那么几条,而 136 往往是藏在最前面的那条引线。刷完这道题再去看 268 缺失数字、645 错误的集合,你会发现它们的核心套路高度相似:数组里大部分元素成对出现,只有一个或少数几个“落单”,利用异或的抵消特性就能在常数空间内找出答案。
这也是为什么我一直建议初学者不要盲目追求刷题数量。像 136 这种题干极短、解法极短的题目,反而是性价比最高的学习材料。你把这道题的前因后果想清楚,后面好几个“异或家族”的题都能少走弯路。我当年第一次做这题时也觉得太简单,后来在面试中被人追问到“为什么异或能消掉偶数次出现的数字”时,才意识到自己只是记住了代码,并没有真正理解这个运算。这篇文章的定位就是帮你把那层窗户纸捅破。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 哈希表和排序:能 AC 但不是最优解的两种常规思路
既然要聊解法,就得先把题目现有的几条路都摆出来,看看它们各自的代价。不要一上来就否定哈希,它是绝大多数人最自然的第一反应,也是面试里很好的“基准答案”。只有当你知道基准方案的样子,才能真正理解异或方案到底优化了什么。
2.1 哈希表:第一反应就是它
哈希表解法不需要动脑,遍历一遍数组,把每个数字出现的次数记下来,再遍历哈希表找次数为 1 的元素。代码如下:
python复制class Solution:
def singleNumber(self, nums: List[int]) -> int:
count = {}
for num in nums:
count[num] = count.get(num, 0) + 1
for num, c in count.items():
if c == 1:
return num
return -1
时间 O(n),空间 O(n),在 LeetCode 上可以顺利通过。还有另一种更聪明的哈希写法:用集合做“成对消除”。遍历数组,如果数字不在集合中就加入,如果已经在集合中就删除。因为除了目标元素外,其他数字都出现两次,一次加入一次删除,最后集合里剩下的唯一元素就是答案:
python复制class Solution:
def single
