LeetCode 41. 缺失的第一个正数 | C++ 哈希表基础解法
📌 题目描述
题目级别:困难 (Hard)
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
-
示例 1:
输入:
nums = [1,2,0]输出:
3解释:范围 [1,2] 中的数字都在数组中。
-
示例 2:
输入:
nums = [3,4,-1,1]输出:
2解释:1 在数组中,但 2 没有。
🚀 解法一:常规哈希表 (直觉解法,空间 O(N))
寻找"缺失的数字",最符合人类直觉的方法就是:把见过的数字都记在小本本上,然后从 1 开始挨个往上数,看看谁没被记过。
在代码实现中,这个"小本本"最合适的数据结构就是哈希集合(unordered_set)。
- 遍历原数组,把所有的正整数都扔进
unordered_set中记录下来(自动去重且查找时间为 O(1)O(1)O(1))。 - 初始化一个目标值
target = 1,不断递增,去哈希表里查。 - 查不到的那个数字,就是缺失的第一个正数!
💻 C++ 代码实现
cpp
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
// 使用哈希集合记录所有出现过的正整数
unordered_set<int> seen;
for (int num : nums) {
if (num > 0) {
seen.insert(num);
}
}
// 从 1 开始逐个检查谁没出现过
int target = 1;
while (true) {
// 如果在哈希表中找不到 target,说明这就是缺失的最小正数
if (seen.find(target) == seen.end()) {
return target;
}
target++;
}
}
};
🏆 解法二:原地哈希 / 循环排序 (面试满分终极解,空间 O(1))
题目给出了极其严苛的条件:时间 O(N)O(N)O(N) 且空间 O(1)O(1)O(1)。这意味着我们不能开辟新的哈希表,只能把原数组本身当作哈希表来用!
对于一个长度为 NNN 的数组,它里面能装下的"连续正数"最多也就是 1, 2, 3... N。
如果数组里装满了 1 到 N,那缺失的正数就是 N + 1;如果不满,缺失的正数一定在 [1, N] 之间。
因此,我们可以制定一个规矩:数字 i 必须老老实实呆在索引 i - 1 的位置上(即数字 1 放在索引 0,数字 2 放在索引 1...)。这就好比"一个萝卜一个坑"。
操作步骤:
- 数字归位 :遍历数组,只要当前萝卜(数字)是个有效正数,且没呆在正确的坑位上,我们就把它交换到它该去的地方。换过来的新萝卜如果也不对,就继续换。
- 查岗核对:等所有萝卜都归位了,我们再从头巡查一遍。如果发现哪个坑里装的不是正确的萝卜,那个坑对应的数字就是缺失的!
💻 C++ 代码实现
cpp
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
// 第一阶段:将所有在 [1, n] 范围内的萝卜归位
for (int i = 0; i < n; i++) {
// 只要当前数字是有效正数,且不在正确的坑位上,就不断进行交换
while (nums[i] > 0 && nums[i] <= n && nums[i] != nums[nums[i] - 1]) {
// 将 nums[i] 送回到属于它的索引位置 (nums[i] - 1)
swap(nums[i], nums[nums[i] - 1]);
}
}
// 第二阶段:查岗,寻找第一个坑位不对的数字
for (int i = 0; i < n; i++) {
// 索引 i 应该对应数字 i + 1
if (nums[i] != i + 1) {
return i + 1; // 找到了缺失的最小正数
}
}
// 如果 1 到 n 都整整齐齐排好了,那缺失的就是 n + 1
return n + 1;
}
};