LeetCode 268. 丢失的数字
题目描述


题意分析
数组长度为
n,包含0到n范围内的n个互不相同的整数。完整范围一共有n + 1个数,因此恰好缺少一个,返回这个没有出现的值。缺失值可能是零、范围中间的数,也可能是上界
n。数组没有保证有序,但保证已有元素互不重复且都在范围内。进阶要求线性时间和常数额外空间,因此不能依赖额外集合记录全部出现情况。
解法:异或抵消
核心思路
[!blue]
异或满足三个性质:相同的数异或为零,零与某个数异或仍是这个数,而且交换顺序或改变分组不会影响结果。只要让完整候选范围
0..n与数组中的全部值一起参与异或,已经存在的每个值就会出现两次并抵消,缺失值只出现一次,最后留下的就是它。不需要另外建立候选数组。遍历输入时,下标
i已经依次提供了0..n - 1,唯一缺少的候选是n,所以先令ans = n。每轮再把当前下标i和当前数组值nums[i]一起异或到ans中,就同时枚举了候选与实际值。下标和同位置的数组值不必相等,它们也不需要当场抵消。交换律与结合律允许把不同轮次出现的相同值配在一起,因此输入的排列顺序不影响答案。
循环结束时,
ans等于完整候选集合与实际数组的累计异或。若缺失的是零,其他值全部抵消后结果就是零;若缺失的是n,初始化时放入的n没有对应值可抵消,也会被正确保留。整个过程只读输入,不需要排序或求和。
解题步骤
- 令
ans = nums.length,先加入候选范围的上界n。- 遍历每个下标
i,执行ans ^= i,加入对应候选值。- 再执行
ans ^= nums[i],加入实际出现的值。- 遍历完成后返回累计结果
ans。
代码实现
class Solution {
public int missingNumber(int[] nums) {
int ans = nums.length;
for (int i = 0; i < nums.length; i++) {
// 下标和数组值中成对出现的数字会被异或抵消。
ans ^= i;
ans ^= nums[i];
}
return ans;
}
}
func missingNumber(nums []int) int {
ans := len(nums)
for i, num := range nums {
// 只出现一次的那个范围数字就是缺失值。
ans ^= i
ans ^= num
}
return ans
}
复杂度分析
- 时间复杂度:$O(n)$,每个元素执行固定次数的异或运算,只遍历一次。
- 空间复杂度:$O(1)$,只保存累计异或值和循环下标,不修改输入。
关键点总结
[!green]
- 把完整候选范围与实际出现值合在一起,使已有值成对抵消。
- 下标提供
0..n - 1,初始化为n补全候选范围。- 异或抵消依赖题目保证的值域与唯一性,与输入是否有序无关。
易错点总结
[!yellow]
- 将
ans初始化为零却没有另外加入n,会漏掉候选范围的最后一个数。- 只异或输入元素,没有加入完整候选范围,无法保证已有值都成对抵消。
- 使用赋值
ans = i ^ nums[i],会丢掉此前轮次的结果;这里需要累计异或。- 因为候选范围有序,就直接对输入使用下标与值的二分判断;输入数组本身并不保证有序。
- 将“缺失零时结果为零”当成无答案,混淆合法数值与失败标记;本题保证缺失值一定存在。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 136. 只出现一次的数字 | 简单 | 同样靠相同值异或抵消,本题需补上完整范围中的每个值。 |
| 260. 只出现一次的数字 III | 中等 | 维护异或抵消关系定位少数异常值;本题将下标与数值异或以找缺失值,该题用非零位分成两组分别抵消。 |
| 389. 找不同 | 简单 | 维护异或抵消关系定位少数异常值;本题将下标与数值异或以找缺失值,该题异或两个字符串以找新增字符。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!