手撕Hard--缺失的第一个正数

简介: 手撕Hard--缺失的第一个正数

给你一个未排序的整数数组 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 <= 105

-231 <= nums[i] <= 231 - 1

方法一:最简单的思路,hash表把所有正数存下来,然后遍历hash表,缺失值就找到了。(比官方的易理解,官方是用数组标记)

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        unordered_map<int,int> hash;
        for(int i=0;i<nums.size();i++){
            if(nums[i]>0)hash[nums[i]]++;
        }
        int res=0;
        while(hash[++res]);
        return res;
    }
};

leetcode官方 :

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size();
        for (int& num: nums) {
            if (num <= 0) {
                num = n + 1;
            }
        }
        for (int i = 0; i < n; ++i) {
            int num = abs(nums[i]);
            if (num <= n) {
                nums[num - 1] = -abs(nums[num - 1]);
            }
        }
        for (int i = 0; i < n; ++i) {
            if (nums[i] > 0) {
                return i + 1;
            }
        }
        return n + 1;
    }
};
 

方法二:置换法(详细注释)

除了打标记以外,我们还可以使用置换的方法,将给定的数组「恢复」成下面的形式:


如果数组中包含 x∈[1,N],那么恢复后,数组的第 x−1个元素为 x。


在恢复后,数组应当有 [1, 2, ..., N] 的形式,但其中有若干个位置上的数是错误的,每一个错误的位置就代表了一个缺失的正数。以题目中的示例二 [3, 4, -1, 1] 为例,恢复后的数组应当为 [1, -1, 3, 4],我们就可以知道缺失的数为 2。

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size(); // 获取数组的长度
        // 第一次遍历数组,将每个正整数 nums[i] 放置到索引为 nums[i] - 1 的位置上
        // 即将 1 放到索引 0,将 2 放到索引 1,以此类推,直到 n 放到索引 n - 1
        for (int i = 0; i < n; ++i) {
            // 只处理大于 0 且小于等于 n 的元素,并且确保 nums[i] 不在正确位置上
            while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
                swap(nums[nums[i] - 1], nums[i]); // 将 nums[i] 放到正确的位置上
            }
        }
        // 第二次遍历数组,找到第一个不在正确位置上的数,即缺失的最小正整数
        for (int i = 0; i < n; ++i) {
            if (nums[i] != i + 1) { // 如果 nums[i] 不等于 i + 1,表示缺失 i + 1
                return i + 1;
            }
        }
        // 如果数组中包含 1 到 n 的所有正整数,则缺失的是 n + 1
        return n + 1;
    }
};

方法三:原线性表标记


设数组的长度为n,那么答案一定是在[1, n + 1]之间的一个整数,最坏的情况是1到n都出现在数组中了,即答案会是n+1,否则一定会缺少其中某个数字,那么问题就转化成了统计数组中1到n的出现情况;

难点就在于如何只用常数级别的额外空间,这时候就可以想到使用原数组去标记,因为数组的长度正好就是n;

首先遍历一次数组,让本身小于等于0或大于n的数全部变成0,这样就会让数组中只留下[1, n]之间的数,方便我们后续处理;由于出现的不同数字个数是小于等于n的,因而原数组的空间肯定是足够的;

再次遍历数组,对于每个大于0的数t(即在[1, n]内),我们令nums[t - 1] = -1,用于标记t出现在数组中过;此外在修改nums[t - 1]前,先用一个临时变量x保存nums[t - 1]的值,如果发现x > 0,则还需要迭代地进行标记,直到最后标记的位置的原始值为0或-1(0表示这个地方原来的值不在[1,n]之间,这个值我们不关心从而该位置可以被随意使用;-1表示这个地方已经被标记过);

  • 最后遍历一次数组,nums[i] = -1表示i+1出现过,所以当第一次发现nums[i]不为-1时就可以返回i + 1了;如果全都是-1则表示[1, n]全部出现了,返回n + 1。
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size(), ans = n + 1; // 获取数组长度,并初始化结果为 n+1
        auto judge = [&](int x){return x >= 1 && x <= n;}; // 定义 lambda 表达式,用于判断数字是否在有效范围内
        for(int& m : nums){ // 将不在有效范围内的数字置为 0
            if(!judge(m)) m = 0;
        }
        for(int i = 0; i < n; i ++){ // 将出现过的正整数标记在数组中,负数表示已经出现过
            if(nums[i] > 0){
                int t = nums[i];
                nums[i] = 0;
                while(t > 0){ // 不断迭代,直到 t <= 0
                    int x = nums[t - 1];
                    nums[t - 1] = -1; // 将出现过的正整数标记为 -1
                    t = x;
                }  
            }
        }
        for(int i = 0; i < n; i ++){ // 寻找第一个未被标记的数,其索引 + 1 即为结果
            if(nums[i] != -1){
                ans = i + 1;
                break;
            }
        }
        return ans; // 返回结果
    }
};
相关文章
|
6月前
|
算法 测试技术
枚举(蓝桥练习)(反倍数、特别数的和、找到最多的数、小蓝的漆房、小蓝和小桥的挑战)
枚举(蓝桥练习)(反倍数、特别数的和、找到最多的数、小蓝的漆房、小蓝和小桥的挑战)
|
5月前
|
存储 算法 数据挖掘
LeetCode题目41:缺失的第一个正数
LeetCode题目41:缺失的第一个正数
|
6月前
蓝桥备战-区间嵌套--前缀和做法
蓝桥备战-区间嵌套--前缀和做法
44 0
|
6月前
|
C语言
C语言第四十四弹---调整奇偶数顺序
C语言第四十四弹---调整奇偶数顺序
|
11月前
|
C语言
第十四弹--打印1-100之间的素数
第十四弹--打印1-100之间的素数
|
人工智能 图计算
LeetCode--缺失的第一个正数(41)和 接雨水(42)
LeetCode--缺失的第一个正数(41)和 接雨水(42)
66 0
|
存储 算法 Python
Leedcode 链表两数相加 Python包含反思过程
Leedcode 链表两数相加 Python包含反思过程
88 0
Leedcode 链表两数相加 Python包含反思过程
|
算法 Java
Map与Set高频面试算法题(只出现一次的数字,复制带随机指针的链表,宝石与石头,旧键盘,前k个高频单词)(Java实现)
给一个非空整数数组,只有一个元素出现了一次,剩余的元素都出现了两次,,请找出那个只出现一次的数字
Map与Set高频面试算法题(只出现一次的数字,复制带随机指针的链表,宝石与石头,旧键盘,前k个高频单词)(Java实现)
Codeforces1486 C2.Guessing the Greatest (hard version)(交互题+奇怪的二分)
Codeforces1486 C2.Guessing the Greatest (hard version)(交互题+奇怪的二分)
48 0