(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 小时前
奥赛一本通 1447 靶形数独
算法·深度优先
梦帮科技4 小时前
AI 音乐产品的发布工程:验证门、数据发布、回滚与生产运维纪律
数据结构·数据库·架构·node.js·音视频·动态规划·推荐算法
泯泷4 小时前
那段文字是谁删的?Yjs 14 正式版之前,一套删除归属方案的实现与边界
前端·javascript·算法
hold?fish:palm4 小时前
34 合并K个升序链表
javascript·算法·链表
C++ 老炮儿的技术栈4 小时前
我们在设计tcp协议时,要传一个字符串过去,报文:头十长度十内容,是否要把‘\0‘也填入,长度是否包含‘\0‘
开发语言·数据结构·c++·mfc·c
Navigator_Z6 小时前
LeetCode //C - 1252. Cells with Odd Values in a Matrix
c语言·算法·leetcode
语戚7 小时前
力扣 1621. 大小为K的不重叠线段的数目:动态规划(Java 实现)
java·算法·leetcode·动态规划·力扣·dp
青山木8 小时前
Hot 100 --- 最长有效括号
java·数据结构·算法·leetcode·动态规划
这料鬼有毒9 小时前
二刷hot100-1143.最长公共子序列
算法·leetcode·动态规划
政企项目老覃10 小时前
边缘 AI 推理部署:安防零售场景下的模型裁剪与端侧落地实践
人工智能·程序人生·算法·性能优化·vllm