【LeetCode-数组】缺失的第一个正数

题目描述

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

输入: [1,2,0]
输出: 3

输入: [3,4,-1,1]
输出: 2

输入: [7,8,9,11,12]
输出: 1

说明: 你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。
题目链接: https://leetcode-cn.com/problems/first-missing-positive/
做这一题之前,可以先做一下找到所有数组中消失的数字(思路2)

思路1

使用哈希表来做,题目要求常数级别的额外空间,所以不能额外定义哈希表,但我们可以就在输入数组上构建哈希表。遍历数组,假设当前的元素为 nums[i],由于是找出 [1, n] 之间未出现的最小整数,所以,我们将 nums[abs(nums[i])-1] 设为负数,使用 abs(nums[i]) 的原因是因为 nums[i] 可能在之前的遍历过程中以及被设为负数了,但下标是一个大于等于 0 的数字,所以需要取绝对值。例如,假设 nums[1] = 3,nums[2] = 6,那么遍历到 nums[1] 时,我们将 nums[2] 设为 -6(nums[nums[1]-1] = -nums[nums[1]-1]),代表 2+1=3 出现过。当遍历结束时,我们再遍历一遍 nums,如果 nums[i]>=0 的话,说明 i+1 没有在 nums 中出现,返回 i+1。

还有一个问题,就是输入的 nums 中可能包含负数。比如,nums 中包含 -1 但不包含 1,那么我们将 nums[abs(-1)-1] 设为了负数,表明 1 出现了,但实际上 1 并没有出现。这个问题的解决办法是:我们首先对输出数组 nums 遍历一遍,将其中的负数变为 0,然后在将 nums[abs(nums[i])-1] 设为负数的过程中,如果 nums[abs(nums[i])-1]==0,我们就将 nums[abs(nums[i])-1] 设为 -(num.size()+1),这样的话,在后序的过程中,nums[abs(nums[i])-1] 不会对其他的元素产生影响。具体代码如下:

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        for(int i=0; i<nums.size(); i++){ // 将负数转为0
            if(nums[i]<0) nums[i]=0;
        }

        for(int i=0; i<nums.size(); i++){ // 将nums[abs(nums[i])-1]设为相反数
            int idx = abs(nums[i])-1;
            if(idx>=0 && idx<nums.size()){
                if(nums[idx]==0) nums[idx]=-nums.size()-1;
                else nums[idx] = -abs(nums[idx]); //这样可以保证一个数nums[i]出现多次时,nums[abs(nums[i])-1]一直为负数
            }
        }

        for(int i=0; i<nums.size(); i++){
            if(nums[i]>=0) return i+1;
        }
        return nums.size()+1;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

还可以这样写:
先将小于等于 0 的元素转为数组中出现的任意一个正数,然后求解。代码如下:

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        if(nums.empty()) return 1;

        int t = 0;  // 任意的一个正数
        for(int i=0; i<nums.size(); i++){
            if(nums[i]>0){
                t = nums[i];
                break;
            }
        }
        if(t<=0) return 1;

        for(int i=0; i<nums.size(); i++){
            if(nums[i]<=0) nums[i]=t;  // 将小于等于0的数转为t
        }

        for(int i=0; i<nums.size(); i++){
            if(abs(nums[i])-1>=0 && abs(nums[i])-1<nums.size()){
                nums[abs(nums[i])-1] = -abs(nums[abs(nums[i])-1]);
            }
        }

        for(int i=0; i<nums.size(); i++){
            if(nums[i]>=0) return i+1;
        }
        return nums.size()+1;
    }
};

这种写法个人感觉更容易理解,时间复杂度也是 O(n)。

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

思路2

遍历数组,假设当前的数字 nums[i],记为 x:

  • 如果 nums[i]==i+1,则将 i++;
  • 否则,如果 x>=1 && x<=nums.size(),则我们将 x 放到 nums[x-1] 的位置上。举个例子,假设数组为 [3,1,2],假设当前的数字为第一个数字 3,也就是 x = 3,则我们将 3 放到第 2 个位置(位置从 0 开始计数),则一次交换后数组为 [2,3,1];更详细的例子如下

    图片来自这篇题解

交换完成后,我们遍历数组,假设当前下标为 i,如果 i+1!=nums[i],则返回 i+1;如果对于数组的每一个下标都有 i+1==nums[i],则返回 nums.size()+1,例如数组为 [1,2,3],则没有出现的最小正整数为 nums.size()+1=3+1=4。

代码如下:

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        if(nums.empty()) return 1;

        int i = 0;
        while(i<nums.size()){
            if(i+1==nums[i]) i++;  // 当前数字已经放到了正确的位置上,处理下一个数字,i++
            else{
                int x = nums[i];
                if(x>=1 && x<=nums.size() && x!=nums[x-1]){  // 注意x!=nums[x-1],防止[1,1]的情况下出现死循环
                    swap(nums[i], nums[nums[i]-1]);    // 不能用x,要用nums[i]
                }
                else i++; // 当前位置的数字超过数组下标范围,不能交换,则处理下一个数字,i++
            }
        }

        for(int i=0; i<nums.size(); i++){
            if(nums[i]!=i+1) return i+1;
        }
        return nums.size()+1;   // 注意,返回nums.size()+1
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

相关题目

1、找到所有数组中消失的数字:https://www.cnblogs.com/flix/p/12857693.html

参考

思路 2 参考了 https://leetcode-cn.com/problems/first-missing-positive/solution/javade-6chong-jie-fa-de-tu-wen-xiang-jie-wei-yun-s/

posted @ 2020-05-26 22:14  Flix  阅读(359)  评论(1编辑  收藏  举报