数组缺一个数时,优先用求和法(0到n理论和减实际和),其次哈希集合、原地交换或异或法;关键先确认范围是否连续、起始值及能否修改数组。

数组元素范围已知且连续时,用求和公式最稳
如果数组是 0 到 n 的整数缺一个(比如长度为 n 的数组本该含 0~n 共 n+1 个数),直接用数学法:理论总和减去实际总和就是缺失值。它不改原数组、时间 O(n)、空间 O(1),且无溢出风险(用 long long 更保险)。
常见错误是误用 n*(n+1)/2 却没确认起始是否为 0 或范围是否真连续——比如数组是 [1,2,4,5] 缺 3,但若你按 0~5 算就错了。
实操建议:
- 先确认题目隐含条件:是否从
0或1开始?是否严格连续? - 计算理论和时用
static_cast<long long>(n) * (n + 1) / 2</long>避免int溢出 - 遍历累加时用相同类型,别让
sum是int而理论和是long long
int missingNumber(const vector<int>& nums) {
int n = nums.size();
long long expected = static_cast<long long>(n) * (n + 1) / 2;
long long actual = 0;
for (int x : nums) actual += x;
return expected - actual;
}数组含重复或范围未知时,必须用哈希集合
一旦数组可能有重复、负数、或缺失多个数字(比如 [3,0,1] 缺 2,但你也得知道上限是 3),求和法失效。此时得明确「缺失」的定义域——通常题目会说“在 0 到 n 中缺一个”,那你就建一个 unordered_set 存所有出现的数,再从 0 到 n 扫一遍。
立即学习“C++免费学习笔记(深入)”;
性能上,哈希查找均摊 O(1),总时间 O(n),但空间升到 O(n);有人试图用 vector<bool></bool> 替代,但要注意下标越界——比如 n=3 时你要查 0~3,vector 至少要开 4 位。
实操建议:
- 别用
set(红黑树),unordered_set足够且更快 - 循环检查范围必须是
[0, nums.size()],不是[0, nums.size()-1] - 插入前不用判重,
unordered_set::insert本身幂等
int missingNumber(const vector<int>& nums) {
unordered_set<int> seen(nums.begin(), nums.end());
int n = nums.size();
for (int i = 0; i <= n; ++i) {
if (seen.find(i) == seen.end()) return i;
}
return -1; // 不会到达
}原地交换法适合“数值即下标”类题目,但易写错边界
当题目明确说数组由 0 到 n-1 的整数构成、仅缺一个,且允许修改原数组时,可把每个数放到对应下标位置(即让 nums[i] == i),最后扫一遍找第一个不满足的位置。这是真正的 O(1) 空间解法,但逻辑稍绕,容易陷入死循环或越界。
关键陷阱是交换条件判断:必须确保 nums[i] != i 且 nums[i] < n 且 nums[nums[i]] != nums[i],否则可能把已归位的数又换走,或对 n(超出下标)做访问。
实操建议:
- 内层
while循环必须检查nums[i] < n,否则nums[nums[i]]会越界 - 交换后不要立即
i++,因为换进来的新值未必就位,得继续处理 - 最终返回
i,不是nums[i]——因为此时nums[i] != i,缺的就是i
int missingNumber(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; ++i) {
while (nums[i] != i && nums[i] < n) {
int target = nums[i];
if (nums[target] == target) break; // 已存在,避免死循环
swap(nums[i], nums[target]);
}
}
for (int i = 0; i < n; ++i) {
if (nums[i] != i) return i;
}
return n;
}位运算异或法简洁但适用场景极窄
利用 a ^ a == 0 和 a ^ 0 == a,把 0~n 所有数和数组所有数一起异或,成对出现的抵消,只剩缺失数。它和求和法一样快且省空间,但只适用于“缺且仅缺一个、范围确定、无重复”的情况。
最容易错的是异或范围:数组长 n,完整集是 0 到 n(共 n+1 个数),所以循环要跑 i = 0 到 n,同时把 nums[i] 异或进去——但 nums 只有 n 个元素,最后一个 i == n 时不能访问 nums[n]。
实操建议:
- 先初始化
ans = n(即把最大数带上),再对i = 0到n-1异或i和nums[i] - 别写成两个分开的循环,容易漏一个数
- 异或顺序无关,但变量类型保持
int即可,无需扩大
int missingNumber(const vector<int>& nums) {
int ans = nums.size(); // 先置为 n
for (int i = 0; i < nums.size(); ++i) {
ans ^= i ^ nums[i];
}
return ans;
}真正难的不是选哪种方法,而是读题时立刻判断清楚:范围是否闭合?能否修改数组?是否允许多解?这些定性判断比写代码还容易出错。


















