题目
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
示例 1:
输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。
示例 2:
输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。
示例 3:
输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正数 1 没有出现。
提示:
1 <= nums.length <= 105
-231 <= nums[i] <= 231 - 1
答案
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
// 第一步:将所有非正数替换为n+1(或任何大于n的值),这样它们就不会干扰后续的逻辑
for (int i = 0; i < n; ++i) {
if (nums[i] <= 0 || nums[i] > n) {
nums[i] = n + 1;
}
}
// 第二步:遍历数组,将每个元素abs值对应的索引位置上的数变为负数(标记为已出现)
for (int i = 0; i < n; ++i) {
int index = abs(nums[i]) - 1;
if (index < n && nums[index] > 0) {
nums[index] = -nums[index];
}
}
// 第三步:找到第一个未被标记为正数的索引(即仍为正数的索引)+1
for (int i = 0; i < n; ++i) {
if (nums[i] > 0) {
return i + 1;
}
}
// 如果所有正数都已出现,则返回n+1
return n + 1;
}
};
算法原理
-
清理无效元素
遍历数组nums,将所有非正数(即小于或等于0的数)以及大于数组长度n的数替换为一个特定的值(这里选择n+1),这个值在后续步骤中不会被用作索引,因此不会干扰算法的逻辑。这一步的目的是确保后续步骤中,只有有效的正整数索引会被考虑。 -
标记已出现的正整数
再次遍历数组nums,对于每个元素nums[i]
(此时它应该是一个正整数或n+1),我们取其绝对值并减去1,得到一个索引index。这个索引对应的是我们想要标记的正整数(即index + 1)。然后,我们检查nums[index]
的值,如果它是正数(即未被标记过),则将其变为负数,表示该正整数(index + 1
)已经出现过。这一步利用了数组索引和正整数之间的对应关系,通过修改数组元素的值来记录哪些正整数是存在的。 -
寻找缺失的正整数
最后,我们再次遍历数组nums,寻找第一个未被标记为正数的索引i。由于数组索引从0开始,而题目要求的是正整数,所以我们返回i + 1作为第一个缺失的正整数。如果遍历完整个数组都没有找到未被标记为正数的索引,说明从1到n的所有正整数都已经在数组中出现过,那么缺失的正整数就是n+1。