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]),因为前面的标记可能已经把某些位置变成了负数,但我们要读的是原始数字的值。
相关推荐
脱氧核糖核酸__1 小时前
LeetCode热题100——73.矩阵置零(题目+题解+答案)
c++·算法·leetcode·矩阵
Mr_Xuhhh1 小时前
深入理解单链表的递归反转:从原理到实现
算法·leetcode·职场和发展
智者知已应修善业1 小时前
【51单片机数码管+蜂鸣器的使用】2023-6-14
c++·经验分享·笔记·算法·51单片机
艾莉丝努力练剑2 小时前
【Linux线程】Linux系统多线程(七):<线程同步与互斥>线程同步(下)
java·linux·运维·服务器·c++·学习·操作系统
迷途之人不知返2 小时前
算法类型:双指针类型
算法
c++逐梦人2 小时前
C++ RAII流式日志库实现
开发语言·c++
吴可可1232 小时前
三点绘圆弧的几何实现
算法
t***5442 小时前
还有哪些设计模式适合现代C++
开发语言·c++·设计模式
Wave8452 小时前
C++ 面向对象基础:类、访问权限,构造函数,析构函数
开发语言·c++