题目链接
题目描述
给定一个数组 nums
,其中包含从 1
到 N
的整数,但缺失了其中的两个。请找出这两个缺失的整数。要求时间复杂度为 O(N)
,空间复杂度为 O(1)
。
示例:
- 输入:
[1]
→ 输出:[2,3]
- 输入:
[2,3]
→ 输出:[1,4]
示例分析
- 常规情况:
- 输入
[1,3,4]
,本应包含1-5
,缺失2
和5
。
- 输入
- 边界情况:
- 输入
[1]
,本应包含1-3
,缺失2
和3
。
- 输入
- 大数测试:
- 输入
[1,2,...,999]
,缺失1000
和1001
。
- 输入
算法思路
核心思想:位运算 + 分组异或
- 异或运算:
- 将所有数组元素和完整的
1
到N
异或,得到temp
。temp
等于两个缺失数的异或结果。
- 将所有数组元素和完整的
- 找不同位:
- 找到
temp
中任意一个为1
的二进制位diff
,用于将数分为两组。
- 找到
- 分组异或:
- 根据
diff
位的值,将数组元素和完整序列中的数分为两组,分别异或得到两个缺失数。
- 根据
边界条件与注意事项
- 数组长度:
- 输入数组长度为
N-2
,需确保遍历范围正确。
- 输入数组长度为
- 负数处理:
- 使用算术右移处理符号位,保证负数正确参与位运算。
- 溢出处理:
- 使用
unsigned int
避免进位左移的未定义行为。
- 使用
代码实现
class Solution
{
public:
vector<int> missingTwo(vector<int>& nums)
{
// Step 1: 异或所有元素和完整序列,得到缺失数的异或结果
int temp = 0;
for (int num : nums) temp ^= num;
for (int i = 1; i <= nums.size() + 2; i++) temp ^= i;
// Step 2: 找到区分两个数的比特位
int diff = 0;
while (((temp >> diff) & 1) == 0) diff++;
// Step 3: 分组异或
int a = 0, b = 0;
for (int num : nums)
{
if ((num >> diff) & 1) b ^= num;
else a ^= num;
}
for (int i = 1; i <= nums.size() + 2; i++)
{
if ((i >> diff) & 1) b ^= i;
else a ^= i;
}
return {a, b};
}
};
关键步骤解析
- 异或运算:
- 遍历数组和完整序列
1
到N
(N = nums.size() + 2
),所有出现两次的数异或结果为0
,最终temp
为两缺失数的异或值。
- 遍历数组和完整序列
- 找不同位:
- 缺失数不同,至少存在一个二进制位不同。找到该位
diff
,将数分为两组。
- 缺失数不同,至少存在一个二进制位不同。找到该位
- 分组异或:
- 每组中,除缺失数外,其他数均出现两次,异或后抵消。最终每组异或结果即为缺失数。
对比暴力枚举法
对比维度 | 位运算法 | 暴力枚举法 |
---|---|---|
时间复杂度 | O(N) | O(N²)(遍历每个数检查是否在数组) |
空间复杂度 | O(1) | O(N)(哈希表存储数组元素) |
实现难度 | 需理解位运算和分组逻辑 | 简单,但效率低 |
适用场景 | 大规模数据,内存敏感 | 小规模数据,无需优化 |
总结
位运算法通过异或和分组策略,高效解决了缺失数字问题,时间复杂度和空间复杂度均为最优。其核心在于利用异或的归零律和恒等律,将问题分解为独立的二进制位处理,避免了额外空间的使用,适用于大规模数据场景。