LeetCode 41. 缺失的第一个正数:从基础解法到原地哈希

题目描述(题目链接)

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

进阶要求:请你实现时间复杂度为 On并且只使用常数级别额外空间的解决方案。

示例:

输入:nums = 1,2,0,输出:3

输入:nums = 3,4,-1,1,输出:2

输入:nums = 7,8,9,11,12,输出:1

算法一:排序 + 线性遍历(基础解法)

核心思路:

先对数组进行升序排序。排序后,正数会集中在数组的后半部分。我们只需要从第一个正数开始遍历,检查它是否等于我们期望的连续正整数(从 1 开始)。如果不等,则缺失;如果遍历完都连续,则返回下一个期望值。

代码实现:

cpp 复制代码
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int expect = 1; // 期望找到的第一个正数
        
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] <= 0) continue; // 跳过非正数
            if (i > 0 && nums[i] == nums[i-1]) continue; // 跳过重复元素
            
            if (nums[i] != expect) {
                return expect; // 出现断层,返回期望值
            }
            expect++;
        }
        return expect;
    }
};

复杂度分析:

时间复杂度:On logn,主要消耗在排序上。

空间复杂度:O logn 或 O1,取决于排序算法的实现。

算法二:哈希表(直观解法)

核心思路:

利用哈希集合记录数组中出现的所有数字。然后从 1 开始逐个递增判断是否在集合中,第一个不在集合中的数字即为答案。

代码实现:

cpp 复制代码
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        unordered_set<int> numSet;
        for (int num : nums) {
            numSet.insert(num);
        }
        
        int expect = 1;
        while (numSet.count(expect)) {
            expect++;
        }
        return expect;
    }
};

复杂度分析:

时间复杂度:On ,哈希表插入和查询平均为 O1。

空间复杂度:On,需要额外的哈希表存储数据。

算法三:原地哈希 / 置换法(最优解法)

核心思路:

长度为n的数组,缺失的第一个正数一定在 1n+1范围内。

我们可以将数组本身作为哈希表:

  • 遍历数组,对于数字 x(满足 1≤x≤n),将其交换到它"应该在"的位置,即索引 x-1 处。
  • 通过不断的交换,让尽可能多的数字归位
  • 再次遍历数组,第一个满足 numsi != i+1 的位置,i+1 就是缺失的第一个正数。
  • 如果所有位置都正确,则缺失的是 n+1。

代码实现:

cpp 复制代码
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size();
        
        // 第一步:原地置换
        for (int i = 0; i < n; i++) {
            // 当 nums[i] 在 [1, n] 范围内,且没有放在正确的位置上时,进行交换
            // nums[nums[i] - 1] != nums[i] 用于处理重复元素,防止死循环
            while (nums[i] > 0 && 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;
            }
        }
        
        // 如果所有位置都正确,说明缺失的是 n+1
        return n + 1;
    }
};

复杂度分析:

时间复杂度:On 。虽然嵌套了 while,但每个数字最多被交换一次到正确位置,总交换次数不超过 n次。

空间复杂度:O1。只使用了常数个变量,没有额外数组。

相关推荐
ZhengEnCi4 小时前
2026年10月30日AI早报:Gemini 4 Carbon登顶、DeepSeek融资1055亿、智元机器人上岗实测
人工智能·算法
强哥聊算力4 小时前
《算力江湖·Intel帝国往事》第三集:安迪·格鲁夫的铁血时代:Intel为什么能把对手逼到墙角?
运维·人工智能·科技·算法·ai·gpu算力
Shaoshing4 小时前
LeetCode100 11. 盛最多水的容器
算法·leetcode
raindayinrain4 小时前
c++新特性--实用新特性
c++
Soneswy4 小时前
C++基础篇——类与对象(中)
开发语言·c++
骑着蜗牛撵大象3275 小时前
558 万美元训出 671B 模型:DeepSeek-V3 算法与系统的协同拆解
分布式·算法·moe·deepseek-v3·mla·fp8训练
Eyfcom5 小时前
〖阶段性回顾〗物流系统架构与算法的 Day 1-5 知识图谱与实战答疑
算法·系统架构·知识图谱·物流管理系统·快递管理系统
xunberg5 小时前
【Agent 生产化手记-02】Agent 怎么向人提问
人工智能·算法·架构
海绵宝宝转agent5 小时前
Leetcode二叉树算法个人理解
笔记·学习·算法·leetcode·深度优先
Rain的Java大神之路5 小时前
面试被LeetCode 27(移除元素)问懵?别再用两层 for 循环了:一道“简单题”吃透双指针原地删除,附面试加分话术 + 11 个避坑清单
后端·leetcode·面试