最近在LeetCode上刷到一道很经典的题,题号268,英文名叫 Missing Number,中文一般叫“丢失的数字”。题目描述特别简单:给定一个包含 [0, n] 中 n 个数的数组,找出该范围中没有出现在数组中的那个数。但就是这么一道“简单题”,我见过不少人用哈希表、求和公式做完就扔了,很少有人去深挖题目背后的东西。直到我看到有人用位运算写出了一行核心逻辑的解法,才意识到这道题其实是一把非常好的钥匙——它能串起位运算的几个核心规则,还能让你在面试中展示出“我不是只会暴力解”的思维深度。
这篇文章我想完整记录一下我对这道题的理解过程:从最朴素的思路出发,到为什么位运算是这条路里最优雅的方案,再到异或解法的底层原理、代码实现、边界情况,以及它和哈希表、求和公式之间的复杂度博弈。最后我还会延伸几个位运算的常见变体,帮你把这一套思路从“会做一道题”变成“会做一类题”。如果你正准备面试,或者刚接触 C++ 算法题,这篇文章应该能给你一些不一样的启发。
1. 先拆题:题目给了你哪些隐藏信息
这道题表面看是个“查找缺失元素”的问题,但如果只盯着“查找”两个字,很容易一上来就想到哈希表或者排序,然后问题就变成了“怎么把缺失的那个找出来”。但我想说的是,先别急着写代码,把题目给你的信息全部列出来,你会发现有一些线索是被低估了的。
先看题目描述:给定一个包含 [0, n] 中 n 个数的数组 nums,找出缺失的那个数。举个例子,如果 n = 3,那么完整的序列是 [0, 1, 2, 3],而数组可能只给了 [3, 0, 1],那缺失的就是 2。
注意这里有几个关键信息:
- 数组的长度是
n,但数的范围是[0, n],也就是说一共有n + 1个数,但只给了n个位置,所以一定恰好有一个数缺席。 - 范围是从
0开始连续到n,不是从 1 开始的,这意味着0的缺失是一种合法的边界情况。 - 数组中的元素是“无序”的,题目没规定必须有序。
- 数组中的元素不重复,因为没有哪个数会出现两次,除非
n个位置装不下n+1个不重复的数。
这几个信息组合起来,其实已经暗示了一条很重要的路:这个数组的长度和数值范围存在一个天然的“差值关系”。数组有 n 个元素,完整序列有 n + 1 个元素,缺失的那个就是两者之间的桥梁。顺着“差”这个思路想,很多人会想到求和公式:先把 0 到 n 全部加起来,再减去数组里所有数的和,差值天然就是缺失的那个数。
求和公式确实能解,而且写起来非常短。但它有一个隐患,后面我会详细讲。这里想强调的是,题目描述里其实还藏了一个容易被忽略的点:“只缺失一个数”意味着你可以把所有元素和所有下标建立映射关系。这个“下标”可能就是解题的突破点。如果我们把数组的每个位置也当成一个“数”,跟数组里的值放到一起看,你会发现什么?下标 0 到 n-1,值是 [0, n] 中的 n 个数,除了缺失的那个,其他每个数都会出现两次:一次作为值,一次作为下标(或者反过来)。这时候,位运算里的异或就变得非常有用了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 异或解法核心原理:为什么“自反性”在这里如此契合
在我讲异或解法之前,先带大家快速过一遍异或(XOR)运算的四个基本规则。这几个规则不是八股文,而是理解后面所有代码的关键。
- 归零律:
a ^ a = 0。任何数和自己异或,结果是 0。 - 恒等律:
a ^ 0 = a。任何数和 0 异或,结果还是它自己。 - 交换律:
a ^ b = b ^ a。 - 结合律:
(a ^ b) ^ c = a ^ (b ^ c)。
前两条是最核心的。如果把一个数和自己异或,它会消掉;如果和 0 异或,它会保留。这种特性天然适合用来找“只出现一次”或“出现奇数次的数”。
那放到“丢失的数字”这个场景里怎么用?思路是这样的:我们先把所有下标异或一遍,再跟数组中所有的值异或一遍。在数组里,除了那个缺失的数之外,其他每个数都会出现两次——一次是作为这个数本身,一次是对应的下标。异或运算遇到两个相同的数会互相抵消变成 0,所以那些成对出现的数全部会消掉。最后剩下的,就是既没有在下标集合里找到对应,也没有在原数组里出现两次的那个数,也就是缺失的数字。
这个“下标”和“值”配对抵消的过程,是整套解法的灵魂。很多人第一次看代码觉得“看不懂为什么这么短”,其实就是没理解这个配对逻辑。
直接看代码,C++ 实现如下:
cp复制
