LeetCode 260. 只出现一次的数字 III
题目描述

题意分析
数组中恰好有两个不同的整数各出现一次,其余每个整数都出现两次。返回这两个只出现一次的数,顺序不限。
需要在线性时间、常数额外空间内完成。整数可能为零或负数,判断依据是它们完整的二进制位模式,不能假设两个目标都为正数。
解法:异或分组
核心思路
[!blue]
异或满足
x ^ x = 0、x ^ 0 = x,且交换运算顺序不影响结果。把所有元素异或后,成对数字全部抵消,只剩两个目标数的异或xor = a ^ b。但它还不是某个目标,需要把a、b分开。因为
a != b,xor至少有一位为1,表示两个目标在该位上不同。选取其中任意一位作为分组依据,两个目标必然进入不同组;相同数字的位模式完全一致,它们的两份又必然进入同一组。因此每组内部再做异或,重复数字都会抵消,只留下该组唯一的目标。
lowbit = xor & -xor可以只保留xor最低的一个1。补码取负会将这一位右侧的零仍保留为零、这一位保留为一,而更高位与原数相反,所以按位与后只有这一位留下。代码只累积选定位为零的那一组,得到
first;再利用xor ^ first求出另一个目标。不需要真的创建两个数组,分组仅体现在条件判断中。
解题步骤
- 遍历数组,将所有元素异或到
xor,得到两个目标的异或值。- 计算
lowbit = xor & -xor,确定一位能区分两个目标的掩码。- 再次遍历,把满足
(num & lowbit) == 0的数字异或到first。- 返回
first和xor ^ first,无需对结果排序。
代码实现
class Solution {
public int[] singleNumber(int[] nums) {
int xor = 0;
for (int num : nums) {
xor ^= num;
}
// 两个单次数在这一位不同,用它分组后重复数仍会组内抵消。
int lowbit = xor & -xor;
int first = 0;
for (int num : nums) {
if ((num & lowbit) == 0) {
first ^= num;
}
}
return new int[] {
first,
xor ^ first
};
}
}
func singleNumber(nums []int) []int {
xor := 0
for _, num := range nums {
xor ^= num
}
// 两个单次数在这一位不同,用它分组后重复数仍会组内抵消。
lowbit := xor & -xor
first := 0
for _, num := range nums {
if num&lowbit == 0 {
first ^= num
}
}
return []int{
first,
xor ^ first,
}
}
复杂度分析
- 时间复杂度:$O(n)$。两次遍历数组,每个元素只执行常数次位运算。
- 空间复杂度:$O(1)$。只保存总异或值、分组掩码和一个组的异或结果,不建立分组容器。
关键点总结
[!green]
- 总异或先消除重复项,再用两个目标的差异位把它们分开。
- 正确分组必须同时满足:两个目标进不同组,同一重复数字的两份进同一组。
- 选中任意一个差异位都可以,最低位只是最方便提取的一位。
- 已知
a ^ b和其中一个目标,再异或一次就能恢复另一个。
易错点总结
[!yellow]
- 不能直接判断
num & xor是否为零来分组:xor可能包含多个差异位,两个目标都可能与它按位与得到非零值。xor & (xor - 1)是清除最低位的1,并不是提取它;取单个位掩码应使用xor & -xor。- 掩码可能位于任意位置,
num & lowbit应与零比较,不能与数值1比较。- 掩码依赖最终总异或,必须先完成第一次遍历,不能一边计算总异或一边按临时掩码分组。
- 有符号整数按补码做位运算,即使选中符号位也能分组;掩码为负数不代表算法失效。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 136. 只出现一次的数字 | 简单 | 本题先根据两个单次值的异或差异位分组,再在每组复用原题的异或抵消。 |
| 137. 只出现一次的数字 II | 中等 | 同样处理少量例外值,原题其他值出现三次,本题其他值出现两次但例外有两个。 |
| 补充题 192. 数组中两个只出现一次的数 | 中等 | 都用异或结果找区分两组唯一值的位;补充题还要求升序输出。 |
| 268. 丢失的数字 | 简单 | 维护异或抵消关系定位少数异常值;本题用非零位分成两组分别抵消,该题将下标与数值异或以找缺失值。 |
| 389. 找不同 | 简单 | 维护异或抵消关系定位少数异常值;本题用非零位分成两组分别抵消,该题异或两个字符串以找新增字符。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!