1. 题目链接
LeetCode 268. 丢失的数字
2. 题目描述
给定一个包含 [0, n]
范围内 n
个不同整数的数组 nums
(实际长度为 n
),找出数组中缺失的那个数字。
示例:
- 输入:
nums = [3,0,1]
→ 输出:2
(缺失数字为2
) - 输入:
nums = [0,1]
→ 输出:2
(缺失数字为2
)
3. 示例分析
- 缺失中间值:
nums = [0,1,3,4]
→ 缺失2
。
- 缺失最大值:
nums = [0,1,2]
→ 缺失3
(数组长度n=3
,范围为[0,3]
)。
- 空数组:
- 若
nums = []
→ 缺失0
(题目保证n ≥ 1
,实际无需处理)。
- 若
4. 算法思路
核心思想:异或运算的归零律与恒等律
- 异或性质:
- 归零律:
a ^ a = 0
- 恒等律:
a ^ 0 = a
- 交换律:
a ^ b = b ^ a
- 归零律:
- 问题转化:
- 假设完整数组为
[0,1,2,...,n]
,实际数组nums
缺少其中一个数。 - 对完整数组和实际数组进行异或运算,重复的数会被抵消,最终结果为缺失值。
- 假设完整数组为
实现步骤:
- 遍历实际数组:将所有元素异或到结果
ret
。 - 遍历完整数组:将
[0,1,...,n]
中的每个数异或到ret
。 - 最终结果:
ret
即为缺失的数字。
5. 边界条件与注意事项
- 输入数组有效性:
- 题目保证数组元素唯一且范围正确,无需额外处理重复或越界值。
- 缺失最大值的处理:
- 若缺失
n
,第二个循环中i
的范围为0~n
,仍能正确异或得到结果。
- 若缺失
- 异或运算顺序无关性:
- 异或满足交换律,遍历顺序不影响最终结果。
6. 代码实现
class Solution
{
public:int missingNumber(vector<int>& nums) {int ret = 0;// 第一轮异或:处理实际数组for (int num : nums) ret ^= num;// 第二轮异或:处理完整数组 [0, 1, ..., n]for (int i = 0; i <= nums.size(); i++) ret ^= i;return ret;}
};
算法分步解析
-
初始化结果变量:
int ret = 0;
ret
初始为0
,因为0 ^ a = a
。
-
第一轮异或遍历实际数组:
for (int num : nums) ret ^= num;
- 假设实际数组为
[3,0,1]
,则ret
结果为3 ^ 0 ^ 1 = 2
。
- 假设实际数组为
-
第二轮异或遍历完整数组:
for (int i = 0; i <= nums.size(); i++) ret ^= i;
- 完整数组为
[0,1,2,3]
,此时ret
计算为2 ^ 0 ^ 1 ^ 2 ^ 3 = 3
。
- 完整数组为
-
返回结果:
return ret;
- 最终结果为
3
,即缺失的数字。
- 最终结果为
与暴力枚举法的对比
方法 | 时间复杂度 | 空间复杂度 | 核心思想 |
---|---|---|---|
异或法 | O(n) | O(1) | 利用异或抵消重复元素 |
哈希表法 | O(n) | O(n) | 存储已出现元素,查找缺失值 |
数学求和法 | O(n) | O(1) | 计算理论总和与实际和的差值 |
暴力枚举法 | O(n²) | O(1) | 遍历检查每个数是否在数组中 |
异或法的优势
- 无额外空间:仅需常数空间,适合内存敏感场景。
- 避免溢出风险:相较于求和法,异或法无需处理大数溢出问题。
- 高效性:两次线性遍历即可解决问题。
总结
异或法通过巧妙的位运算,将时间复杂度控制在 O(n)
,同时避免使用额外空间,是解决“缺失数字”类问题的最优方案。其核心在于利用异或运算的数学性质,将重复元素抵消,最终定位缺失值。实际应用中,该方法还可扩展至其他需要“去重”或“找不同”的场景,例如只出现一次的数字等问题。
关键点:
- 异或运算的归零律和恒等律是算法的基础。
- 两次遍历分别处理实际数组和完整数组,确保所有元素成对抵消。