只出现一次的数字 图解题解
这道题到底在问什么
- nums
- [4,1,2,1,2,5,5,3,3,6,6]
- 输出
- 4(只有 4 出现了一次)
最优解:为什么这么做
一句话答案:LeetCode 136 只出现一次的数字最优解是异或一趟扫描:利用「相同的数异或抵消成 0、任何数异或 0 还是自己」,把数组所有数依次异或进一个累加器,成对出现的数全部抵消,最后剩下的就是那个落单的数。时间 O(n)、空间 O(1),恰好满足题目「线性时间、常量空间」的硬要求。
这道题的隐藏约束是常量空间
数组里除了一个数只出现一次,其余每个数都恰好出现两次,要把落单的那个找出来。题目额外钉了两条硬要求:线性时间、常量空间。找出现次数本身不难,难的是这两条约束把最顺手的工具全排除了——排序要 O(n log n) 时间,哈希表计数要 O(n) 额外空间。这道题真正考的,是知不知道有一种运算能在不占空间的前提下「记住并抵消」成对的数。
为什么只出现一次的数字要用异或
先看直觉解卡在哪:用哈希表统计每个数出现几次,再挑出计数为 1 的,时间 O(n) 达标,但表本身要 O(n) 空间,不满足常量空间。我们需要的是一种「聚合」——扫过所有数后只留一个变量,且成对的数在聚合过程中自动消失。
异或(按位比较,两位不同得 1、相同得 0)正好有这两条性质:a ⊕ a = 0,相同的数碰面就归零;a ⊕ 0 = a,零是不改变结果的单位元。于是把全部数异或进一个累加器 ans,出现两次的数两两抵消成 0,一串 0 异或下来不留痕迹,最后 ans 里剩的就是那个没有搭档的数。整段代码只有一个循环、一行 ans ^= x。
成对的数隔得很远,为什么也能抵消
异或满足交换律和结合律,一长串异或的结果与运算顺序无关——可以在脑子里把相同的数重排到相邻位置再算,结果不变。所以两个 5 无论一个在开头一个在结尾,最终都等价于紧挨着异或、抵消成 0。拿题目示例 [4,1,2,1,2,5,5,3,3,6,6] 验证:1、2、5、3、6 各自成对消光,只剩 4,与答案一致。这也解释了为什么累加器从 0 起步最干净:0 是单位元,第一个数异或进来天然等于它自己,不需要任何特殊处理。
复杂度怎么算,这招的适用边界在哪
时间 O(n):数组扫一遍,每个数做一次异或。空间 O(1):只用一个变量 ans,与数组规模无关——这正是哈希表方案给不了的。
但要清楚这招的边界:异或抵消靠的是「恰好成对」。如果题目改成其余数都出现三次、只有一个出现一次(LeetCode 137),三个相同的数异或剩下它自己,抵消失效;那类题得按二进制位统计每一位上 1 出现的总次数,对 3 取模来还原答案。面试里被追问变体时,能点出「异或只消偶数次出现」这条本质,比背出代码更值钱。
▶ 动画逐步走查(共 25 步)——想跟着动画一帧帧对照就展开
- 3记住两句话:相同的数异或抵消成 0,任何数异或 0 还是它自己。把全部数异或一遍,成对的都消掉,剩下的就是落单那个。
- 4先准备一个累加器 ans = 0。指针还没出发,接下来从下标 0 开始,把每个数依次异或进 ans。
- 5指针走到下标 0,这一格的值是 4。把它异或进累加器:当前 ans = 0,下一步算 0 ⊕ 4。
- 6算出 0 ⊕ 4 = 4,ans 更新为 4。
- 7指针走到下标 1,这一格的值是 1。把它异或进累加器:当前 ans = 4,下一步算 4 ⊕ 1。
- 8算出 4 ⊕ 1 = 5,ans 更新为 5。
- 9指针走到下标 2,这一格的值是 2。把它异或进累加器:当前 ans = 5,下一步算 5 ⊕ 2。
- 10算出 5 ⊕ 2 = 7,ans 更新为 7。
- 11指针走到下标 3,这一格的值是 1。把它异或进累加器:当前 ans = 7,下一步算 7 ⊕ 1。
- 12算出 7 ⊕ 1 = 6,ans 更新为 6。
- 13指针走到下标 4,这一格的值是 2。把它异或进累加器:当前 ans = 6,下一步算 6 ⊕ 2。
- 14算出 6 ⊕ 2 = 4,ans 更新为 4。
- 15指针走到下标 5,这一格的值是 5。把它异或进累加器:当前 ans = 4,下一步算 4 ⊕ 5。
- 16算出 4 ⊕ 5 = 1,ans 更新为 1。
- 17指针走到下标 6,这一格的值是 5。把它异或进累加器:当前 ans = 1,下一步算 1 ⊕ 5。
- 18算出 1 ⊕ 5 = 4,ans 更新为 4。
- 19指针走到下标 7,这一格的值是 3。把它异或进累加器:当前 ans = 4,下一步算 4 ⊕ 3。
- 20算出 4 ⊕ 3 = 7,ans 更新为 7。
- 21指针走到下标 8,这一格的值是 3。把它异或进累加器:当前 ans = 7,下一步算 7 ⊕ 3。
- 22算出 7 ⊕ 3 = 4,ans 更新为 4。
- 23指针走到下标 9,这一格的值是 6。把它异或进累加器:当前 ans = 4,下一步算 4 ⊕ 6。
- 24算出 4 ⊕ 6 = 2,ans 更新为 2。
- 25指针走到下标 10,这一格的值是 6。把它异或进累加器:当前 ans = 2,下一步算 2 ⊕ 6。
- 26算出 2 ⊕ 6 = 4,ans 更新为 4。
- 27扫到末尾,所有成对的数都两两抵消了,ans = 4。它正是数组里那个只出现一次的数,也就是最终答案。
⚠️ 容易写错的地方
✗ 错:用哈希表统计每个数出现几次
✓ 对:直接用异或一趟累加
哈希表要 O(n) 额外空间,不满足题目「常量空间」的硬要求
✗ 错:ans 初始值写成 nums[0]
✓ 对:ans 从 0 开始更稳
从 0 起步逻辑统一;a⊕0=a,第一个数异或进来天然就是它自己,不必特殊处理
✗ 错:以为异或只能用在两个数之间
✓ 对:异或可连续累加,满足交换律和结合律
顺序无所谓,成对的数无论隔多远都会抵消,最后只剩落单那个
完整代码(Python / C++ / Java)
Python
def singleNumber(nums):
ans = 0 # 累加器,从 0 开始
for x in nums:
ans ^= x # 把每个数异或进来
return ans # 成对的都抵消,剩下落单的C++
int singleNumber(vector<int>& nums){
int ans = 0;
for (int x : nums) ans ^= x;
return ans;
}Java
public int singleNumber(int[] nums) {
int ans = 0;
for (int x : nums) ans ^= x;
return ans;
}复杂度
时间
O(n)
从头到尾扫一遍数组,每个数只异或一次
空间
O(1)
只用一个累加器 ans,不开额外数组或哈希表
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 只出现一次的数字 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么异或能做到常量空间?+
因为只需要一个变量 ans 累加结果,不依赖数组大小开额外空间,所以是 O(1) 空间、O(n) 时间,正好卡题目要求。
如果改成「其余数都出现三次、只有一个出现一次」,异或还行吗?+
不行。两次才能靠 a⊕a=0 抵消;出现三次抵消不掉。那种题要按二进制位统计每位出现次数,对 3 取模来还原落单的数。
异或运算满足交换律和结合律吗?+
满足。所以数组里数的先后顺序不影响结果,成对的数无论相隔多远都会被抵消,最终只剩那个落单的。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 只出现一次的数字 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。