(17)LeetCode 41. 缺失的第一个正数

LeetCode 41. 缺失的第一个正数

📝 题目描述

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

示例 1:

复制代码
输入:nums = [1,2,0]
输出:3

示例 2:

复制代码
输入:nums = [3,4,-1,1]
输出:2

示例 3:

复制代码
输入:nums = [7,8,9,11,12]
输出:1

💡 解题思路

核心观察

  • 对于长度为 n 的数组,答案(最小缺失正整数)只可能在 [1, n+1] 范围内
  • 如果 1 ~ n 全都出现,那么答案就是 n+1;否则答案就是 1 ~ n 中第一个没出现的数。

算法思想:原地哈希

我们可以利用数组下标来标记某个数是否存在。

目标 :将每个值 x(满足 1 <= x <= n)放到数组下标 x-1 的位置上,即最终让 nums[x-1] == x

这样,处理完以后,从头扫描数组,第一个 nums[i] != i+1 的位置 i,就说明 i+1 缺失。

步骤

  1. 遍历数组 ,对于每个位置 i,只要 nums[i][1, n] 范围内,并且它没有放在正确的位置上,就将其与目标位置 nums[i]-1 上的元素交换。
  2. 再次遍历 ,找到第一个 nums[i] != i+1,返回 i+1;如果全符合,返回 n+1

🧩 代码实现(C++)

cpp 复制代码
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size();
        // 第一步:将每个正整数放到它应该在的位置
        for (int i = 0; i < n; i++) {
            while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
                swap(nums[i], nums[nums[i] - 1]);
            }
        }
        // 第二步:扫描第一个位置不对的数
        for (int i = 0; i < n; i++) {
            if (nums[i] != i + 1)
                return i + 1;
        }
        return n + 1;
    }
};

🔍 代码详解

外层 for 循环 + 内层 while

cpp 复制代码
for (int i = 0; i < n; i++) {
    while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
        swap(nums[i], nums[nums[i] - 1]);
    }
}
  • nums[i] >= 1 && nums[i] <= n :只关心在有效范围内的数。
  • nums[nums[i] - 1] != nums[i] :检查目标位置是否已经放置了正确的数。如果不是,就交换。
  • 使用 while 而不是 if 的原因是:交换后,新换到 i 位置的数可能仍然需要调整,所以要持续处理直到当前位置 i 不再需要移动。

为什么不会死循环?

每次交换都会把一个数放到它的最终位置(下标 nums[i]-1),一个数最多被交换一次,所以总交换次数不超过 n,整体复杂度 O(n)。

第二次扫描

cpp 复制代码
for (int i = 0; i < n; i++) {
    if (nums[i] != i + 1)
        return i + 1;
}
return n + 1;
  • 此时数组已经"部分有序":所有 1~n 内的数都被放到了对应的下标处。
  • 第一个 nums[i] != i+1 的位置即为答案。
  • 若全部满足,说明 1~n 都存在,返回 n+1

🧪 示例推演

nums = [3, 4, -1, 1] 为例:

步骤 操作 数组状态
初始 - [3, 4, -1, 1]
i=0 nums0=3,目标下标2,交换 nums0 和 nums2 [-1, 4, 3, 1]
继续 nums0=-1,不在1,4,退出 while
i=1 nums1=4,目标下标3,交换 nums1 和 nums3 [-1, 1, 3, 4]
继续 nums1=1,目标下标0,交换 nums1 和 nums0 [1, -1, 3, 4]
继续 nums1=-1,退出 while
i=2 nums2=3,已在正确位置(下标2),不交换
i=3 nums3=4,已在正确位置(下标3),不交换

最终数组 [1, -1, 3, 4],扫描:

  • i=0,nums0=1 ✅
  • i=1,nums1=-1 ≠ 2,返回 2。

📊 复杂度分析

  • 时间复杂度O(n)
    外层循环 n 次,内层 while 每轮至少将一个数放到正确位置,总共最多执行 n 次交换,因此整体线性。
  • 空间复杂度O(1)
    只使用了若干临时变量,符合常数级别额外空间的要求。

⚠️ 注意事项

  • 忽略所有小于 1 或大于 n 的数,因为它们不可能放在有效下标上(答案最大为 n+1),不影响最终判断。
  • 重复数字的处理:如果某个位置已经正确(例如 nums[nums[i]-1] == nums[i]),即使 nums[i] 重复,循环条件不满足,不会发生死循环。
相关推荐
卡提西亚2 小时前
leetcode-179. 最大数
python·算法
浩瀚地学2 小时前
【面试算法笔记】0302-哈希表-哈希表实现
java·经验分享·笔记·算法·面试
ECT-OS-JiuHuaShan2 小时前
严格证明:还原论是ASCII,整体论是UTF-8的历史意义和价值
开发语言·人工智能·算法·量子计算
ShallWeL2 小时前
【机器学习】(19)—— 数据特性与标签
人工智能·算法·机器学习
旖-旎3 小时前
《LeetCode 416 分割等和子集》
c++·算法·leetcode·动态规划·背包问题
Jasmine_llq3 小时前
《P15798 [GESP202603 五级] 有限不循环小数》
算法·有限小数判定数论定理·质因子约分检验·枚举生成合法数字·哈希集合去重·快速 io 优化
学计算机的计算基3 小时前
操作系统八股文:进程与线程全面梳理(附调度算法+IPC+锁机制)
java·算法
程序猿乐锅4 小时前
【数据结构与算法 | 第二篇】 双链表的增删改查
数据结构