第一次看到这个题目时,我脑海中立即浮现出经典的"只出现一次的数字"问题。这类问题通常给定一个非空整数数组,其中某个元素只出现一次,其余都出现两次(或特定次数),要求找出那个"单身数字"。最基础的版本用异或运算就能轻松解决,但本题的"|||"变体显然增加了难度。
先回顾下基础解法:对所有数字进行异或运算,由于a^a=0且a^0=a,成对出现的数字会相互抵消,最终结果就是唯一的单身数字。这个方法时间复杂度O(n),空间复杂度O(1),堪称完美。
python复制def singleNumber(nums):
res = 0
for num in nums:
res ^= num
return res
但当所有数字出现三次(或奇数次)时,这个解法就失效了。因为a^a^a=a,无法通过简单异或来区分出现一次和三次的数字。这就是我们需要更高级位运算技巧的原因。
我的思路转向了"统计每位上1出现的次数"。如果一个数字出现三次,那么它的每个二进制位上的1也会累加三次。我们可以统计所有数字在各个位上的1的计数,然后对3取余:
这种方法需要遍历32位整数的每一位(假设是32位系统),对每个位遍历整个数组统计1的个数。虽然时间复杂度是O(32n)=O(n),但实际运行效率可能不如预期。
经过深入研究,我发现了一种更优雅的解法,使用状态机原理和位掩码技术,可以在一次遍历中解决问题,无需额外的位循环。
设想每个bit位有三种状态:
我们需要设计一个状态转移机制,使得当一个数字出现三次时,状态能循环回到初始值。这可以通过两个变量来实现:
ones:记录当前处于状态1的位twos:记录当前处于状态2的位状态转移规则:
ones中ones中移除,并记录到twos中twos中移除具体实现时,我们需要用位运算来表达这些状态转移:
python复制def singleNumber(nums):
ones, twos = 0, 0
for num in nums:
ones = (ones ^ num) & ~twos
twos = (twos ^ num) & ~ones
return ones
解释关键步骤:
ones ^ num:将当前数字与ones异或,如果是第一次出现会设置对应位,第二次出现会清除& ~twos:确保只有当该位不在twos中时才更新ones(避免第三次出现时错误设置)这种解法同样保持O(n)时间复杂度和O(1)空间复杂度,但实际运行效率比位计数法更高。
这个问题可以进一步抽象为更通用的形式:给定一个数组,其中有一个数字出现p次,其他数字都出现k次(p和k互质),如何找出这个数字?
我们可以扩展之前的位计数思路,统计每位上1的总数后对k取模:
python复制def singleNumber(nums, k):
res = 0
for i in range(32):
count = 0
for num in nums:
count += (num >> i) & 1
res |= (count % k) << i
return res if res < 2**31 else res - 2**32 # 处理负数
对于通用情况,我们需要⌈log₂k⌉个变量来记录状态。例如k=5时需要3个变量(因为2³=8≥5)。状态转移可以通过位运算和掩码来实现,但实现复杂度会显著增加。
在位运算解法中,需要特别注意负数的表示。Python使用补码表示负数,但整数位数是动态的。当处理32位整数时,如果结果的最高位是1,需要手动转换为负数:
python复制if res >= 2**31:
res -= 2**32
必须测试的边界情况包括:
变体:数组中恰好有两个数字出现一次,其他都出现两次。解法:
变体:所有数字出现两次或三次,只有一个出现一次。解法:
xor_sumor_allor_all中每个为1的位,统计该位为1的数字个数xor_sum相同在解决这类问题时,以下位运算技巧非常有用:
异或性质:
a ^ a = 0a ^ 0 = a掩码操作:
x & -xx & (x - 1)x & (x - 1) == 0位设置与清除:
x | (1 << n)x & ~(1 << n)x ^ (1 << n)符号处理:
(x ^ (x >> 31)) - (x >> 31)(x ^ y) >= 0这类位运算技巧在实际工程中有广泛的应用:
在实际编码面试或工程实现中,需要根据具体情况选择算法:
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 | 实现难度 |
|---|---|---|---|---|
| 哈希表 | O(n) | O(n) | 通用解法,易理解 | 低 |
| 位计数 | O(n) | O(1) | k较小的情况 | 中 |
| 状态机 | O(n) | O(1) | 特定k值优化 | 高 |
| 数学法 | O(n) | O(1) | 数值范围有限时 | 中 |
对于本题的k=3情况,状态机解法通常是最优选择,尤其是在资源受限的环境中。
以下是Python的完整实现,包含测试案例:
python复制def singleNumber(nums):
ones, twos = 0, 0
for num in nums:
ones = (ones ^ num) & ~twos
twos = (twos ^ num) & ~ones
return ones
# 测试案例
test_cases = [
([2,2,3,2], 3),
([0,1,0,1,0,1,99], 99),
([-1,-1,-1,-2], -2),
([1], 1),
([1,1,1,2,2,2,3], 3)
]
for nums, expected in test_cases:
result = singleNumber(nums)
assert result == expected, f"Failed: {nums}, got {result}, expected {expected}"
print("All test cases passed!")
对于追求极致性能的场景,可以考虑以下优化:
此外,这个问题还可以从数学角度思考。如果我们计算3*(所有唯一数字的和)减去数组总和,结果将是2倍的单身数字。不过这种方法需要额外空间存储唯一数字集合,空间复杂度为O(n)。