只出现一次的数字 III 图解题解
这道题到底在问什么
- 输入
- nums = [1,2,1,3,2,5]
- 输出
- [3,5](1、2 各出现两次,只有 3 和 5 落单)
最优解:一步一步想明白
- 3两把钥匙:xorAll = 全体异或 = a^b;diff = xorAll 最低位的 1,用它把 a 和 b 分到两组里各自捞出来。下面一帧帧演。
- 4第一趟开始。准备一个累加器 xorAll = 0,从左到右把每个数异或进去。
- 5指针走到下标 0,这一格是 1。把它异或进 xorAll(当前 xorAll = 0)。
- 6异或完成:xorAll 从 0 变成 1。下标 0 及之前的数都已处理(标蓝)。
- 7指针走到下标 1,这一格是 2。把它异或进 xorAll(当前 xorAll = 1)。
- 8异或完成:xorAll 从 1 变成 3。下标 1 及之前的数都已处理(标蓝)。
- 9指针走到下标 2,这一格是 1。把它异或进 xorAll(当前 xorAll = 3)。
- 10异或完成:xorAll 从 3 变成 2。下标 2 及之前的数都已处理(标蓝)。
- 11指针走到下标 3,这一格是 3。把它异或进 xorAll(当前 xorAll = 2)。
- 12异或完成:xorAll 从 2 变成 1。下标 3 及之前的数都已处理(标蓝)。
- 13指针走到下标 4,这一格是 2。把它异或进 xorAll(当前 xorAll = 1)。
- 14异或完成:xorAll 从 1 变成 3。下标 4 及之前的数都已处理(标蓝)。
- 15指针走到下标 5,这一格是 5。把它异或进 xorAll(当前 xorAll = 3)。
- 16异或完成:xorAll 从 3 变成 6。下标 5 及之前的数都已处理(标蓝)。
- 17第一趟结束。所有成对的数互相抵消,xorAll = 6,它正好等于两个落单数 a 与 b 的异或值。
- 18取 xorAll 最低位的那个 1:diff = xorAll &(-xorAll) = 2。a 和 b 在这一位上一定不同,用它把它们分到两组。
- 19第二趟开始。两个累加器 a=0、b=0。按「这个数和 diff 与一下是不是 0」把每个数分进 A 组或 B 组。
- 20指针到下标 0,值 1。1 和 diff(2) 与一下得 0,为 0,分到 B 组。
- 21B 组(红):b 从 0 异或 1 变成 1。每组里成对的数还是会抵消。
- 22指针到下标 1,值 2。2 和 diff(2) 与一下得 2,不为 0,分到 A 组。
- 23A 组(绿):a 从 0 异或 2 变成 2。每组里成对的数还是会抵消。
- 24指针到下标 2,值 1。1 和 diff(2) 与一下得 0,为 0,分到 B 组。
- 25B 组(红):b 从 1 异或 1 变成 0。每组里成对的数还是会抵消。
- 26指针到下标 3,值 3。3 和 diff(2) 与一下得 2,不为 0,分到 A 组。
- 27A 组(绿):a 从 2 异或 3 变成 1。每组里成对的数还是会抵消。
- 28指针到下标 4,值 2。2 和 diff(2) 与一下得 2,不为 0,分到 A 组。
- 29A 组(绿):a 从 1 异或 2 变成 3。每组里成对的数还是会抵消。
- 30指针到下标 5,值 5。5 和 diff(2) 与一下得 0,为 0,分到 B 组。
- 31B 组(红):b 从 0 异或 5 变成 5。每组里成对的数还是会抵消。
- 32两趟扫完。A 组异或剩下 a=3,B 组异或剩下 b=5,这两个就是只出现一次的数:[3, 5]。
⚠️ 容易写错的地方
✗ 错:想用哈希表统计次数
✓ 对:用异或分组
哈希表是 O(n) 额外空间,违反题目 O(1) 空间要求;异或法不开数组
✗ 错:diff 取错位(如取最高位或随便一位)
✓ 对:diff = xorAll &(-xorAll) 取最低的 1 位
只要是 xorAll 里值为 1 的位都能分组,但取最低位写法最简洁且一定存在
✗ 错:分组时把同一个数同时异或进 a 和 b
✓ 对:按 (x & diff) 是否为 0 二选一,只进一组
进错组或两组都进会破坏抵消,得不到正确的 a、b
完整代码(Python / C++ / Java)
Python
def singleNumber(nums):
xor_all = 0
for x in nums: # 第一趟:全体异或
xor_all ^= x # 成对抵消,剩 a^b
diff = xor_all & (-xor_all) # 取最低的 1 位
a = b = 0
for x in nums: # 第二趟:按 diff 位分组
if x & diff:
a ^= x # A 组
else:
b ^= x # B 组
return [a, b]C++
vector<int> singleNumber(vector<int>& nums){
long xorAll = 0;
for (int x : nums) xorAll ^= x;
int diff = xorAll & (-xorAll);
int a = 0, b = 0;
for (int x : nums) {
if (x & diff) a ^= x;
else b ^= x;
}
return {a, b};
}Java
public int[] singleNumber(int[] nums) {
int xorAll = 0;
for (int x : nums) xorAll ^= x;
int diff = xorAll & (-xorAll);
int a = 0, b = 0;
for (int x : nums) {
if ((x & diff) != 0) a ^= x;
else b ^= x;
}
return new int[]{a, b};
}复杂度
时间
O(n)
两趟线性扫描,每个元素各看常数次
空间
O(1)
只用 xorAll、diff、a、b 几个整数,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 只出现一次的数字 III 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
diff = xorAll &(-xorAll) 在做什么?+
取出 xorAll 二进制里最低位的那个 1。因为 a≠b,a^b 至少有一位是 1,这一位上 a 和 b 必然一个 0 一个 1,所以能用它把 a、b 分到不同组。
为什么两个落单数一定落在不同组?+
分组依据是「该数 & diff 是否为 0」。diff 对应的位上 a 和 b 一个是 1 一个是 0,所以 a 与 b 与 diff 的结果一个非 0 一个为 0,必进不同组。
如果题目改成只有一个数出现一次呢?+
那就是「只出现一次的数字 I」,一趟全体异或,成对的抵消,xorAll 直接就是答案,连分组都不用。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 只出现一次的数字 III 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。