17.缺失的第一个正数(学习)
给你一个未排序的整数数组 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
解析:
一、清除无效数字:
遍历数组,将所有非正整数(即小于等于0的数)和超出数组长度的数(即大于 n 的数,其中 n 是数组长度)替换为一个不可能在 [1, n] 范围内出现的值,比如 n + 1。这样做是为了避免这些无效数字干扰后续的映射过程。
二、映射正整数到索引位置:
再次遍历数组,对于每个有效的正整数 num(即 1 <= num <= n),我们将其对应的索引 num - 1 上的元素取反(或设置为一个特定的标记值,但这里使用取反是因为它只需要一位操作,且容易在后续检查中恢复)。注意,如果索引 num - 1 上的元素已经是负数(或已经是我们的标记值),则不需要再次取反,因为这意味着该数字之前已经被处理过了(可能是数组中存在重复的数字)。
三、找到第一个未标记的索引:
遍历数组,找到第一个仍然是正数的元素。由于我们已经将有效正整数对应的索引位置上的元素取反(或标记),因此第一个正数对应的索引加1就是缺失的最小正整数。
四、处理所有索引都被标记的情况:
如果遍历完整个数组后都没有找到正数,说明 [1, n] 范围内的所有整数都在数组中出现了(以取反或标记的形式),那么缺失的最小正整数就是 n + 1。
var firstMissingPositive = function(nums) {
const n = nums.length;
// Step 1: 清除无效数字(非正整数),并将超出范围的数替换为n+1(或其他无效值)
for (let i = 0; i < n; i++) {
if (nums[i] <= 0 || nums[i] > n) {
nums[i] = n + 1; // 替换为无效值
}
}
// Step 2: 映射正整数到索引位置(使用正负号或特定值标记)
// 注意:我们需要避免重复映射和错误映射
for (let i = 0; i < n; i++) {
let num = Math.abs(nums[i]);
if (num <= n) {
// 只处理尚未被标记过的、且在范围内的数字
if (nums[num - 1] > 0) {
nums[num - 1] *= -1; // 使用负数标记为已出现
}
// 如果nums[num-1]已经是负数,说明之前已经处理过这个数字了,无需再次处理
}
}
// Step 3: 找到第一个未标记的索引(即正数)
for (let i = 0; i < n; i++) {
if (nums[i] > 0) {
// 如果找到了正数,说明i+1是缺失的最小正整数
return i + 1;
}
}
// 如果所有索引都被标记了(即都是负数),则最小的正整数是n+1
return n + 1;
};