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: nums[0] = -3(负数,跳过)
  • i=1: nums[1] = 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]),因为前面的标记可能已经把某些位置变成了负数,但我们要读的是原始数字的值。
相关推荐
Je1lyfish9 分钟前
CMU15-445 (2025 Fall/2026 Spring) Project#3 - QueryExecution
linux·c语言·开发语言·数据结构·数据库·c++·算法
许彰午11 分钟前
03-二叉树——从递归遍历到非递归实现
java·算法
Brilliantwxx24 分钟前
【C++】 vector(代码实现+坑点讲解)
开发语言·c++·笔记·算法
叼烟扛炮1 小时前
C++第三讲:类和对象(中)
开发语言·c++·类和对象
KuaCpp1 小时前
C++新特性学习
c++·学习
墨染千千秋2 小时前
C/C++ Keywords
c语言·c++
ximu_polaris2 小时前
设计模式(C++)-行为型模式-中介者模式
c++·设计模式·中介者模式
NorburyL2 小时前
DPO笔记
深度学习·算法
老纪的技术唠嗑局2 小时前
深度解析 LLM Wiki / Obsidian-Wiki / GBrain:Agent 时代知识的“自组织”与“自进化”
大数据·数据库·人工智能·算法
CSCN新手听安4 小时前
【Qt】Qt窗口(八)QFontDialog字体对话框,QInputDialog输入对话框的使用,小结
开发语言·c++·qt