LeetCode热题100——41.缺失的第一个正数(题解+答案+要点)

题目:

给你一个未排序的整数数组 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 <= 10^5

  • -2^31 <= nums[i] <= 2^31 - 1

题解:

如果本题没有额外的时空复杂度要求,可以用如下的哈希表方式实现。将数组所有的数放入哈希表,随后从 1 开始依次枚举正整数,并判断其是否在哈希表中。但是空间复杂度为 O(n),不符合题干要求。

cpp 复制代码
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        unordered_set<int> mp;
        for(int x : nums) if(x > 0) mp.insert(x);
        int i = 1;
        while(mp.count(i)) i++;
        return i;
    }
};

正确解法(原地哈希)

核心思想

利用数组本身作为哈希表:

  • 答案只可能落在 [1, n+1] 范围内 ,其中 n = nums.size()

  • 通过修改数组元素的正负号,标记某个正整数是否出现过。

  • 最终第一个正数元素的下标 i 对应的值 i+1 即为缺失的最小正数。

为什么答案只可能在 1 ~ n+1 之间?
  • 如果数组长度为 n,那么最理想的情况是 1,2,3,...,n 全部出现,此时缺失的是 n+1。

  • 如果缺少某个 1~n 之间的数,那么它一定小于 n+1。

  • 对于 ≤0 或 >n 的数字,它们不可能成为"最小的缺失正整数"

算法步骤
  1. 清理无关数字:把所有不在 1, n 范围内的数字改成 n+1(一个无意义的"大数")。

  2. 标记出现过的数字:遍历数组,用负号标记出现过的数字。

    注意:我们只关心 1~n 这些数字是否出现过。假设某个数字 x 在 1~n 之间,那么应该把下标 x-1 位置的数变成负数。

  3. 查找答案:扫描数组,第一个正数所在的下标就是缺失的最小正整数

  4. 边界情况 :如果所有 1~n 都出现过,则答案为 n+1

举例说明

以示例 nums = [3, 4, -1, 1] 为例,n = 4。

第一步:清理无关数字
  • -1 ≤ 0 → 改为 n+1 = 5

  • 其他数字都在 1~4 之间,保持不变

    结果数组:[3, 4, 5, 1]

第二步:标记出现过的数字

遍历每个元素(取绝对值):

  • i=0, num=3 → 标记下标 2 (3-1) 的位置:nums[2] = 5,5 > 0 吗?是 → 变为 -5。数组:[3,4,-5,1]

  • i=1, num=4 → 标记下标 3 (4-1):nums[3] = 1 > 0 → 变为 -1。数组:[3,4,-5,-1]

  • i=2, num=5 → 5 > n (4)?是 → 忽略

  • i=3, num=1 → 标记下标 0 (1-1):nums[0] = 3 > 0 → 变为 -3。数组:[-3,4,-5,-1]

现在负号标记了哪些下标?

  • 下标 0 为负 → 数字 1 出现过

  • 下标 2 为负 → 数字 3 出现过

  • 下标 3 为负 → 数字 4 出现过

  • 下标 1 仍然为正(4) → 数字 2 没有出现过。

第三步:查找答案

扫描:

  • i=0: nums0 = -3(负数,跳过)
  • i=1: nums1 = 4(正数) → 缺失的正整数是 i+1 = 2。

返回 2 ✅

答案:

cpp 复制代码
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
    int n=nums.size();
    // 第一步:把所有不在 [1, n] 范围内的数字改成 n+1(一个无意义的"大数")
    for(int &x:nums) {
        if(x<=0||x>n) x=n+1;
    }
    // 第二步:遍历数组,用负号标记出现过的数字
    // 注意:我们只关心 1~n 这些数字是否出现过。
    // 假设某个数字 x 在 1~n 之间,那么应该把下标 x-1 位置的数变成负数。
    for(int i=0;i<n;i++){
        int num=abs(nums[i]);// 因为可能之前已被标记为负数,所以取绝对值
        if(num<=n){
            if(nums[num-1]>0) nums[num-1]=-nums[num-1];// 如果该位置还不是负数,就将其取反
        }
    }
    // 第三步:扫描数组,第一个正数所在的下标就是缺失的最小正整数
    for(int i=0;i<n;i++){
        if(nums[i]>0) return i+1;// 下标 i 对应数字 i+1
    }
    // 如果所有 1~n 都出现过,则答案是 n+1
    return n+1;
    }
};

要点:

  • 第二步中必须取绝对值 abs(nums[i]),因为前面的标记可能已经把某些位置变成了负数,但我们要读的是原始数字的值。
相关推荐
JieE2125 小时前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack2013 小时前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法
小小杨树14 小时前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色
JieE2121 天前
LeetCode 226. 翻转二叉树|JS 递归超详细拆解,二叉树入门经典题
javascript·算法
JieE2121 天前
LeetCode 104. 二叉树的最大深度|递归思路超详细拆解
javascript·算法
vivo互联网技术1 天前
CVPR 2026 | 全新强化学习框架 BeautyGRPO:重塑真实人像
算法·大模型·cvpr·影像
Darling噜啦啦2 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
clint4562 天前
C++进阶(1)——前景提要
c++
用户497863050732 天前
(一)小红的数组操作
算法·编程语言