1. 问题背景与题目解析
LeetCode 260题"只出现一次的数字III"是一个经典的位运算问题。题目要求在一个整数数组中,恰好有两个元素只出现一次,其余所有元素均出现两次。我们的任务是找出这两个只出现一次的数字。
这个问题是LeetCode 136题"只出现一次的数字"的进阶版本。在136题中,只有一个数字出现一次,其余都出现两次,可以通过简单的异或运算解决。但当有两个独特的数字时,情况就变得复杂了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 位运算基础回顾
在深入解决方案之前,我们需要回顾几个关键的位运算概念:
-
异或运算(XOR):对于两个数字的每一位,相同为0,不同为1。异或运算有几个重要性质:
- 任何数和0异或都是它本身:a ^ 0 = a
- 任何数和自身异或都是0:a ^ a = 0
- 异或运算满足交换律和结合律
-
与运算(AND):两个位都为1时结果才为1。我们可以用它来检查特定位是否为1。
-
补码表示:计算机中负数是用补码表示的,这会影响我们如何获取最右边的1。
3. 解题思路分析
3.1 初步思考
如果我们直接对所有数字进行异或运算,最终会得到两个独特数字的异或结果。例如,假设独特数字是a和b,那么xor = a ^ b。由于a ≠ b,xor一定不为0。
关键问题在于:如何从xor中分离出a和b?
3.2 关键突破点
我们需要找到一个方法将数组分成两部分,使得:
- a和b分别位于不同的部分
- 相同的数字会被分到同一部分
这样,我们就可以对每一部分分别进行异或运算,最终得到a和b。
3.3 寻找区分位
xor中至少有一位是1(因为a ≠ b)。我们可以找到xor中最右边的1(即最低位的1),这个位在a和b中是不同的。我们可以利用这个位来区分a和b。
4. 详细解决方案
4.1 步骤详解
- 对所有数字进行异或运算,得到xor = a ^ b
- 找到xor中最右边的1的位置(即diff位)
- 根据diff位是否为1,将数组分成两部分
- 对每一部分分别进行异或运算,得到a和b
4.2 代码实现
python复制def singleNumber(nums):
# 第一步:获取两个独特数字的异或值
xor = 0
for num in nums:
xor ^= num
# 第二步:找到最右边的1
diff = xor & -xor
# 第三步:分组并分别异或
a, b = 0, 0
for num in nums:
if num & diff:
a ^= num
else:
b ^= num
return [a, b]
4.3 代码解释
-
xor & -xor:这是一个巧妙的技巧,可以找到最右边的1。因为负数在计算机中用补码表示,-xor等于~xor + 1,这样与操作会保留最右边的1。 -
分组时,我们检查每个数字在diff位是否为1。如果是,则归入一组;否则归入另一组。
-
由于相同的数字在diff位相同,它们会被分到同一组,异或后会抵消。而a和b在diff位不同,所以会被分到不同组。
5. 复杂度分析
- 时间复杂度:O(n),我们只需要遍历数组两次
- 空间复杂度:O(1),只使用了常数个额外变量
6. 边界条件与测试用例
6.1 常见测试用例
python复制# 示例1
输入: [1,2,1,3,2,5]
输出: [3,5]
# 示例2
输入: [-1,0]
输出: [-1,0]
# 示例3
输入: [0,1]
输出: [0,1]
6.2 特殊边界情况
- 包含负数的情况:补码表示不影响我们的解法
- 最大最小值:如[2147483647, -2147483648]
- 大数组情况:验证算法效率
7. 常见错误与调试技巧
7.1 常见错误
- 忘记初始化xor为0
- 错误地计算diff位
- 分组条件写反(把if-else逻辑弄反)
- 返回顺序不符合题目要求
7.2 调试技巧
- 打印中间结果:xor和diff的值
- 手动验证分组是否正确
- 对于小数组,手动计算预期结果
8. 位运算优化技巧
8.1 寻找最右边的1
除了xor & -xor,还可以用以下方法:
python复制diff = 1
while (xor & diff) == 0:
diff <<= 1
8.2 其他位运算技巧
- 检查奇偶性:
num & 1 - 交换两个数:
a ^= b; b ^= a; a ^= b - 判断是否为2的幂:
n & (n-1) == 0
9. 相关问题与扩展
9.1 LeetCode类似题目
-
- 只出现一次的数字(基础版)
-
- 只出现一次的数字 II(三个重复)
-
- 错误的集合(找重复和缺失的数字)
9.2 实际应用场景
- 错误检测与纠正(如海明码)
- 数据压缩
- 加密算法
- 网络协议中的校验和
10. 个人解题心得
在实际解决这个问题时,我最初尝试直接用异或运算,发现无法区分两个独特数字。通过分析异或结果的性质,才想到可以利用不同的位来分组。这个问题的关键在于:
- 理解异或运算的性质
- 认识到两个独特数字至少有一位不同
- 找到一种方法利用这个差异位来分组
在实现时,xor & -xor这个技巧非常巧妙,值得记住。对于位运算问题,画二进制表示图往往能帮助理解。
