这道题是数组题里的经典入门题,但非常适合用来训练解题思路的多样性。
同一道题,可以用:
- 额外数组
- 排序
- 哈希表
- 数学公式
来解决。
这篇笔记我会从「最直观」一路推到「最优雅」。
一、题目回顾
给定一个长度为 n 的数组 nums,数组中包含 [0, n] 范围内的 n 个不同数字。
也就是说:
- 本来应该有
n + 1个数字 - 但现在缺失了一个
目标:
找出缺失的那个数字
二、方法一:额外数组
这是最容易想到的解法。
思路
- 新建一个长度为
n + 1的数组 - 遍历
nums,把出现过的数字标记出来 - 再遍历新数组,找到没被标记的位置
代码
class Solution {
public int missingNumber(int[] nums) {
int n = nums.length;
int[] newNums = new int[n + 1];
for (int num : nums) {
newNums[num]++;
}
for (int i = 0; i <= n; i++) {
if (newNums[i] == 0) {
return i;
}
}
return 0;
}
}
分析
时间复杂度:
O(n)
空间复杂度:
O(n)
评价
- 优点:思路简单,非常好理解
- 缺点:额外空间开销大,不够优雅
三、方法二:排序后找位置不匹配
这是利用排序特性的解法。
思路
- 排序后,理想状态是
nums[i] == i - 一旦发现不相等的位置,说明
i就是缺失的数 - 如果都匹配,缺的就是最后一个
n
代码
class Solution {
public int missingNumber(int[] nums) {
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i) {
return i;
}
}
return nums.length;
}
}
分析
时间复杂度:
O(n log n)
空间复杂度:
O(1)(忽略排序栈)
评价
- 优点:实现简单,逻辑直观
- 缺点:排序的时间成本偏高
四、方法三:HashSet 去重查缺
这是**典型的“空间换时间”**思路。
思路
- 把数组元素全部放进
HashSet - 从
0到n遍历 - 第一个不在集合里的数字就是答案
代码
class Solution {
public int missingNumber(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for (int num : nums) {
set.add(num);
}
for (int i = 0; i < nums.length; i++) {
if (!set.contains(i)) {
return i;
}
}
return nums.length;
}
}
分析
时间复杂度:
O(n)
空间复杂度:
O(n)
评价
- 优点:查找速度快,逻辑清晰
- 缺点:需要额外集合空间
五、方法四:数学公式(最推荐)
这是面试官最爱的一种解法。
思路
-
[0, n]的总和是:n * (n + 1) / 2 -
实际数组中的元素和是
sum -
两者之差,就是缺失的数字
代码
class Solution {
public int missingNumber(int[] nums) {
int n = nums.length;
int sum = 0;
for (int num : nums) {
sum += num;
}
int total = n * (n + 1) / 2;
return total - sum;
}
}
分析
时间复杂度:
O(n)
空间复杂度:
O(1)
评价
- 优点:代码最简洁、效率最高
- 缺点:需要对数学公式有敏感度
六、四种方法对比总结
| 方法 | 时间复杂度 | 空间复杂度 | 特点 |
|---|---|---|---|
| 额外数组 | O(n) | O(n) | 最直观 |
| 排序 | O(n log n) | O(1) | 利用有序性 |
| HashSet | O(n) | O(n) | 空间换时间 |
| 数学公式 | O(n) | O(1) | 最优解 |
七、这道题真正想考什么?
这道题的本质并不是“找一个数”,而是考察:
- 是否能从暴力解法逐步优化
- 是否能想到用数学消除多余计算
- 是否具备空间 / 时间复杂度权衡的意识
如果你能在面试中说出:
我知道 4 种解法,最优的是用数学公式
这道题基本稳了。