LeetCode 热题 100 之 41.缺失的第一个正数

核心思路

对于长度为 n 的数组,缺失的最小正整数只可能在 [1, n+1] 范围内:

  • 如果 1 到 n 都出现了,那么答案就是 n+1;
  • 否则,答案就是其中缺失的最小正整数。

为了满足 O (n) 时间复杂度 和 O (1) 空间复杂度 的要求,我们可以利用数组本身作为 "哈希表",将每个数 x(满足 1 ≤ x ≤ n)放到它对应的索引位置 x-1 处。最后遍历数组,第一个不满足 nums[i] == i+1 的位置 i+1 就是答案。


解题步骤

  1. 原地置换 :遍历数组,对于每个元素 nums[i],如果它是 1 到 n 之间的数,并且不在正确的位置(nums[i] != nums[nums[i]-1]),就将它交换到 nums[i]-1 的位置。
  2. 查找缺失 :再次遍历数组,找到第一个位置 i,使得 nums[i] != i+1,返回 i+1。如果所有位置都满足,返回 n+1。

Java 实现

复制代码
public int firstMissingPositive(int[] nums) {
    int n = nums.length;
    for (int i = 0; i < n; i++) {
        // 将 1~n 的数放到对应的位置
        while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
            int temp = nums[nums[i] - 1];
            nums[nums[i] - 1] = nums[i];
            nums[i] = temp;
        }
    }
    // 查找第一个缺失的正整数
    for (int i = 0; i < n; i++) {
        if (nums[i] != i + 1) {
            return i + 1;
        }
    }
    // 1~n 都存在,返回 n+1
    return n + 1;
}

示例验证

示例 1 :nums = [1,2,0]

  • 遍历后数组为 [1,2,0]。
  • 检查:nums[0]=1,nums[1]=2,nums[2]=0 != 3,返回 3。

示例 2 :nums = [3,4,-1,1]

  • 遍历后数组为 [1,-1,3,4]。
  • 检查:nums[0]=1,nums[1]=-1 != 2,返回 2。

示例 3 :nums = [7,8,9,11,12]

  • 遍历后数组不变,所有元素都不在 1~5 范围内。
  • 检查:nums[0]=7 != 1,返回 1。

复杂度分析

  • 时间复杂度:O (n),每个元素最多被交换到正确的位置一次,两次线性遍历。
  • 空间复杂度:O (1),仅使用常数额外空间,原地修改数组。

扩展:官方题解

官方题解的思路是:

利用数组本身作为 "标记容器",通过正负号标记「1~n 范围内的数是否出现过」------ 正数表示未出现,负数表示已出现,最终第一个正数对应的索引 + 1 就是缺失的最小正整数。

逐行代码解释

复制代码
class Solution {
    public int firstMissingPositive(int[] nums) {
        // 步骤1:获取数组长度n(核心:缺失的最小正整数只在[1, n+1]范围内)
        int n = nums.length;

        // 步骤2:将所有非正整数(≤0)替换为n+1(这些数对结果无影响,统一标记为"无效值")
        for (int i = 0; i < n; ++i) {
            if (nums[i] <= 0) {
                nums[i] = n + 1;
            }
        }

        // 步骤3:用"正负号"标记1~n范围内的数是否出现过
        for (int i = 0; i < n; ++i) {
            // 取绝对值:避免之前的标记(负号)影响判断
            int num = Math.abs(nums[i]);
            // 仅处理1~n范围内的数(超出的数无意义)
            if (num <= n) {
                // 将num对应的索引(num-1)位置的数标记为负数,表示num已出现
                // 再次取绝对值:避免重复标记导致变正(比如数组有重复数时)
                nums[num - 1] = -Math.abs(nums[num - 1]);
            }
        }

        // 步骤4:遍历数组,找到第一个正数的索引,返回索引+1(即为缺失的最小正整数)
        for (int i = 0; i < n; ++i) {
            if (nums[i] > 0) {
                return i + 1;
            }
        }

        // 步骤5:如果数组全为负数(1~n都出现了),返回n+1
        return n + 1;
    }
}

关键步骤深度拆解

步骤 2:替换非正整数为 n+1
  • 为什么要替换?:非正整数(0、负数)不是我们要找的 "正整数",留着会干扰后续的标记逻辑(比如负数会被误判为 "已标记");
  • 为什么替换成 n+1?:n+1 是 "超出有效范围的数"(我们只关心 1~n),后续处理时会被过滤掉,不会影响标记。
步骤 3:核心标记逻辑(最关键)
  • 核心公式 :数num对应数组索引num-1(比如数 1 对应索引 0,数 3 对应索引 2);
  • 取绝对值的原因 :
    1. 第一步替换后,nums[i]可能是正数(原正数 / 替换的 n+1),也可能是后续被标记的负数;
    2. 必须用Math.abs(nums[i])拿到原始数值,才能判断它是否在 1~n 范围内;
  • -Math.abs(nums[num-1])的原因 :避免重复标记导致符号反转(比如数组有重复数[2,2]):
    • 第一次处理num=2:nums[1]变为负数;
    • 第二次处理num=2:如果直接写nums[1] = -nums[1],会把负数变回正数,导致标记失效;
    • 用-Math.abs(...)能保证无论原值是正 / 负,最终都变为负数,标记不失效。

完整示例验证(nums = 3,4,-1,1,n=4)

表格

步骤 操作 数组状态 说明
初始 - 3,4,-1,1 原始数组
步骤 2 替换≤0 的数为 5(n+1=5) 3,4,5,1 -1 被替换为 5
步骤 3(i=0) num=3(≤4)→ nums2 = -5 3,4,-5,1 标记数 3 已出现
步骤 3(i=1) num=4(≤4)→ nums3 = -1 3,4,-5,-1 标记数 4 已出现
步骤 3(i=2) num=5(>4)→ 不处理 3,4,-5,-1 5 超出范围,跳过
步骤 3(i=3) num=1(≤4)→ nums0 = -3 -3,4,-5,-1 标记数 1 已出现
步骤 4 遍历找第一个正数 索引 1 的数是 4(正数) 返回 1+1=2

最终结果为 2,和预期一致。

另一示例验证(nums = 1,2,0,n=3)

表格

步骤 操作 数组状态 说明
初始 - 1,2,0 原始数组
步骤 2 替换 0 为 4 1,2,4 0 被替换为 4
步骤 3(i=0) num=1→nums0=-1 -1,2,4 标记 1 已出现
步骤 3(i=1) num=2→nums1=-2 -1,-2,4 标记 2 已出现
步骤 3(i=2) num=4>3→不处理 -1,-2,4 4 超出范围
步骤 4 遍历找第一个正数 索引 2 的数是 4(正数) 返回 2+1=3

最终结果为 3,符合预期。

总结(关键点回顾)

  1. 核心思想:用数组索引映射 1~n 的正整数,通过正负号标记 "数是否出现",无需额外空间;
  2. 关键处理 :
    • 替换非正整数为 n+1,避免干扰标记;
    • 全程用绝对值处理,避免重复标记 / 负号干扰;
  3. 结果判断:第一个正数的索引 + 1 是答案,全负数则返回 n+1;
  4. 复杂度:时间 O (n)(三次线性遍历),空间 O (1)(原地修改),完美满足题目约束。

这种 "标记法" 是原地哈希的经典应用,和之前的 "置换法" 相比,逻辑更简洁,且避免了交换操作的边界问题,是这道题的最优解法之一。

相关推荐
sunburn-2 分钟前
Java 排序算法详细教学:从冒泡排序到快速排序
java·开发语言·数据结构·ide·算法
阳明山水12 分钟前
因果嵌入与流水线范式的本质差异
人工智能·深度学习·算法·机器学习·架构
小静AI工程实验室16 分钟前
MD5 算法详解:哈希原理、标准向量、输入编码与安全边界
算法·安全·哈希算法·md5
小许同学记录成长17 分钟前
几何体编辑算法
qt·算法
gnsnswa20 分钟前
回归岗位价值:CAIE 认证适合人群与能力定位
人工智能·网络协议·职场和发展·产品经理·创业创新·信息与通信·ai写作
土星云SaturnCloud26 分钟前
HRNet-pose 算法全解析:从高分辨率网络原理到土星云 SE110S 边缘部署实战
服务器·人工智能·算法·ai·边缘计算
bksczm29 分钟前
零基础面试题1
jvm·数据结构·算法
m0_7393128732 分钟前
【自动驾驶与机器人技术】之惯性导航与组合导航
算法·机器人·自动驾驶
驭渊的小故事39 分钟前
牛客网算法刷题笔记:哈希表、贪心与动态规划实战
笔记·算法·散列表
careathers1 小时前
【数据结构】二叉树
数据结构·算法