题目描述(题目链接)
给你一个未排序的整数数组 nums,请你找出其中没有出现的最小的正整数。
进阶要求:请你实现时间复杂度为 On并且只使用常数级别额外空间的解决方案。
示例:
输入:nums = 1,2,0,输出:3
输入:nums = 3,4,-1,1,输出:2
输入:nums = 7,8,9,11,12,输出:1
算法一:排序 + 线性遍历(基础解法)
核心思路:
先对数组进行升序排序。排序后,正数会集中在数组的后半部分。我们只需要从第一个正数开始遍历,检查它是否等于我们期望的连续正整数(从 1 开始)。如果不等,则缺失;如果遍历完都连续,则返回下一个期望值。
代码实现:
cpp
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
sort(nums.begin(), nums.end());
int expect = 1; // 期望找到的第一个正数
for (int i = 0; i < nums.size(); i++) {
if (nums[i] <= 0) continue; // 跳过非正数
if (i > 0 && nums[i] == nums[i-1]) continue; // 跳过重复元素
if (nums[i] != expect) {
return expect; // 出现断层,返回期望值
}
expect++;
}
return expect;
}
};

复杂度分析:
时间复杂度:On logn,主要消耗在排序上。
空间复杂度:O logn 或 O1,取决于排序算法的实现。
算法二:哈希表(直观解法)
核心思路:
利用哈希集合记录数组中出现的所有数字。然后从 1 开始逐个递增判断是否在集合中,第一个不在集合中的数字即为答案。
代码实现:
cpp
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
unordered_set<int> numSet;
for (int num : nums) {
numSet.insert(num);
}
int expect = 1;
while (numSet.count(expect)) {
expect++;
}
return expect;
}
};
复杂度分析:
时间复杂度:On ,哈希表插入和查询平均为 O1。
空间复杂度:On,需要额外的哈希表存储数据。
算法三:原地哈希 / 置换法(最优解法)
核心思路:
长度为n的数组,缺失的第一个正数一定在 1n+1范围内。
我们可以将数组本身作为哈希表:
- 遍历数组,对于数字 x(满足 1≤x≤n),将其交换到它"应该在"的位置,即索引 x-1 处。
- 通过不断的交换,让尽可能多的数字归位
- 再次遍历数组,第一个满足 numsi != i+1 的位置,i+1 就是缺失的第一个正数。
- 如果所有位置都正确,则缺失的是 n+1。
代码实现:
cpp
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
// 第一步:原地置换
for (int i = 0; i < n; i++) {
// 当 nums[i] 在 [1, n] 范围内,且没有放在正确的位置上时,进行交换
// nums[nums[i] - 1] != nums[i] 用于处理重复元素,防止死循环
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
swap(nums[i], nums[nums[i] - 1]);
}
}
// 第二步:检查哪个位置上的数字不对
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
// 如果所有位置都正确,说明缺失的是 n+1
return n + 1;
}
};

复杂度分析:
时间复杂度:On 。虽然嵌套了 while,但每个数字最多被交换一次到正确位置,总交换次数不超过 n次。
空间复杂度:O1。只使用了常数个变量,没有额外数组。