这道题目来自LeetCode第136题,是算法面试中的经典问题。给定一个非空整数数组,其中某个元素只出现一次,其余每个元素均出现两次。我们的任务是找出那个只出现一次的数字。
这类问题在实际开发中其实很常见。比如在日志分析时,我们可能需要从大量重复事件中找出异常事件;在数据处理流水线中,可能需要识别出不符合常规模式的数据点。理解这类问题的解法,对培养程序员的位操作思维很有帮助。
最直观的解法是使用哈希表记录每个数字出现的次数:
python复制def singleNumber(nums):
freq = {}
for num in nums:
freq[num] = freq.get(num, 0) + 1
for num in freq:
if freq[num] == 1:
return num
时间复杂度:O(n),需要遍历数组两次
空间复杂度:O(n),需要额外存储哈希表
利用集合去重后计算和的2倍减去原数组和:
python复制def singleNumber(nums):
return 2 * sum(set(nums)) - sum(nums)
时间复杂度:O(n),但sum操作实际上遍历了两次数组
空间复杂度:O(n),需要存储集合
这道题的最优解是利用位运算中的异或(XOR)操作,只需要O(n)时间和O(1)空间:
python复制def singleNumber(nums):
res = 0
for num in nums:
res ^= num
return res
异或运算有几个重要特性:
假设数组是 [4,1,2,1,2]:
最终得到的结果4就是只出现一次的数字。因为成对出现的数字异或后都抵消为0了,最后剩下的就是单独的数字。
虽然题目保证非空数组,但实际工程中应该考虑:
python复制if not nums:
raise ValueError("Input array cannot be empty")
这个方法适用于所有整型数据,包括负数:
python复制assert singleNumber([-1,-1,-2]) == -2
用Python的timeit模块测试10万量级数据:
这就是原题的情况,直接用异或解法。
此时异或不再适用,需要更复杂的位操作:
python复制def singleNumber(nums):
ones, twos = 0, 0
for num in nums:
ones = (ones ^ num) & ~twos
twos = (twos ^ num) & ~ones
return ones
需要分组异或:
python复制def singleNumber(nums):
xor = 0
for num in nums:
xor ^= num
mask = 1
while (xor & mask) == 0:
mask <<= 1
a, b = 0, 0
for num in nums:
if num & mask:
a ^= num
else:
b ^= num
return [a, b]
错误做法:
python复制res = nums[0] # 如果第一个元素就是单独的数字,会漏掉它
for num in nums[1:]:
res ^= num
正确应该初始化为0,确保所有元素都被处理。
在JavaScript中要注意:
javascript复制let res = 0;
for(let num of nums) {
res ^= num;
}
// 需要确保输入都是整数
可以在循环中加入打印语句观察异或过程:
python复制res = 0
for i, num in enumerate(nums):
print(f"Step {i}: {res} ^ {num} = {res ^ num}")
res ^= num
这个问题展示了位运算在算法中的强大威力。类似的技巧还包括:
掌握这些技巧可以大幅提升解决位操作类问题的能力。建议练习LeetCode的位操作标签题目,如: