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。只使用了常数个变量,没有额外数组。

相关推荐
Omics Pro1 小时前
强生:以机理为中心!虚拟细胞世界模型
数据库·人工智能·python·深度学习·算法·机器学习·自然语言处理
FPGA信号处理1 小时前
【凸优化】第一节课(下):保凸运算、分离超平面与 Farkas 引理
人工智能·算法·机器学习
Lhan.zzZ2 小时前
Qt/QML:业务记录单展示、签名确认与 PDF 导出开发
c++·qt·pdf
傲世仙尊2 小时前
类和对象上-class与struct实例化内存对齐与this指针
c++
怕浪猫2 小时前
Agent 的记忆系统怎么设计?面试官想听的是这个
算法·面试·github
C++ 老炮儿的技术栈2 小时前
C++ const 的3个核心作用
服务器·c语言·c++·算法·c
小黄人软件2 小时前
粘贴助手C++版 支持 Windows 7 / 10 / 11 不会出现缺少api-ms-win-core-path-11-1-0.dll
开发语言·c++·windows
有毒的教程2 小时前
Dev-C++ 避免爆栈(Stack Overflow)完整方案
c++·算法·深度优先
程序员老陆2 小时前
从汇编层面理解 C++ 多线程
开发语言·c++·程序设计