LeetCode 137. 只出现一次的数字 II
本节目标
将 32 位整数拆成独立的位计数,按模 3 的余数重建唯一值。
这道题把位运算拓展中的“逐位独立”模型落到具体约束上:其他数都出现三次,只有一个数出现一次,答案的每一位都可以单独恢复。
题意与约束
给定一个非空整数数组 nums,除一个元素只出现一次外,其余元素都恰好出现三次。返回只出现一次的元素。
1 <= nums.length <= 3 * 10^4- 每个
nums[i]都是 32 位有符号整数。 - 题目保证只有一个元素出现一次。
为什么整体异或不再够用
上一题中的异或依赖 a ^ a = 0:成对元素会消去。但同一个数出现三次时,a ^ a ^ a = a,它并不会消失,所有三次出现的数都会混在结果里。
这里需要利用新的重复次数规律。对任意一个二进制位,出现三次的数会在该位贡献 0 或 3 个 1,都满足模 3 后为 0;只有答案的该位会留下 1 的余数。
每一位独立统计
依次处理第 0 到第 31 位。固定某一位后,统计数组中这一位为 1 的次数:
- 次数除以
3的余数为0,答案这一位是0; - 余数不为
0,答案这一位是1。
每一位的计数不会影响其他位,也没有进位,因此可以把 32 位分别恢复到 answer 中。32 位是固定常数,整体仍只需一次数量级的扫描。
负数与第 31 位
题目中的整数按 32 位补码表示。第 31 位是符号位;若它为 1,无符号位模式 answer 表示的数应减去 2^32,才能得到对应的有符号答案。
C++ 先把每个 int 转为 uint32_t 再右移,确保读取的是其低 32 位,不依赖负数右移的实现细节。重建时,若第 31 位为 1,先在 int64_t 中计算 answer - 2^32,再转为 int;这个值已在 32 位 int 的范围内,因此不依赖超范围的无符号转有符号转换。
Python 的 int 是无限宽的,负数右移会符号扩展;但对第 0 到第 31 位,(num >> bit) & 1 恰好仍是该数 32 位补码的对应位。循环结束后同样检查第 31 位,并减去 2^32,把无符号重建结果显式还原为负数。
代码实现
两份源码都进行 32 轮独立计数。C++ 用 uint32_t 保存重建出的位模式;Python 保持普通整数,并在最后显式完成 32 位有符号重建。
- C++
- Python
#include <cstdint>
#include <vector>
using namespace std;
class Solution {
public:
int singleNumber(vector<int>& nums) {
uint32_t answer = 0;
for (int bit = 0; bit < 32; bit++) {
int count = 0;
for (int num : nums) {
count += (static_cast<uint32_t>(num) >> bit) & 1U;
}
if (count % 3 != 0) {
answer |= 1U << bit;
}
}
if ((answer & (1U << 31)) != 0) {
return static_cast<int>(static_cast<int64_t>(answer) - (1LL << 32));
}
return static_cast<int>(answer);
}
};
class Solution:
def singleNumber(self, nums: list[int]) -> int:
answer = 0
for bit in range(32):
count = sum((num >> bit) & 1 for num in nums)
if count % 3:
answer |= 1 << bit
if answer >= 1 << 31:
answer -= 1 << 32
return answer
复杂度分析
- 时间复杂度:对 32 个位各扫描一次数组,为
O(32n),即O(n)。 - 额外空间复杂度:只使用计数器、答案和循环变量,为
O(1)。
易错点
- 沿用整体异或:三个相同数异或后仍是原数,不能消去。
- 只统计到最高有效位:负数的符号位也要参与,因此必须固定处理 32 位。
- C++ 直接右移负数:右移语义不应成为算法前提,应先转为
uint32_t。 - Python 直接返回位模式:当第 31 位为
1时会得到非负的大整数,必须减去2^32。
模式迁移
当“其余元素都出现相同次数,少数元素留下固定余数”且各位互不影响时,可以尝试逐位计数再取模。重复次数改为 k 时,计数改为模 k;但若出现多个不同的剩余元素,或各位之间存在进位、顺序等关系,就需要重新建模,不能直接套用本题。