41. 缺失的第一个正数

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

提示:

  • 0 ≤ n u m s . l e n g t h ≤ 300 0 \le nums.length \le 300 0≤nums.length≤300
  • − 2 31 ≤ n u m s [ i ] ≤ 2 31 − 1 -2^{31} \le nums[i] \le 2^{31} - 1 −231≤nums[i]≤231−1

题解:

法一:

核心思想:

理想情况下,长度为 nnums 应该正好包含 1~n ,如果某个元素 nums[i] 不在范围内,那么在理想情况下,nums 包含的范围应该相应的减小。数组的长度和元素范围是相对应的。

  • 使用两个变量 lr

    1. l 表示遍历到目前为止,nums 已经包含的正整数范围是 [1,l] ,开始时 l = 0 ,表示没有包含任何正整数

    2. r 表示遍历到目前为止,后续出现最优状况的情况下,nums 可能包含的正整数范围是 [1,r] ,开始时 r = n ,因为还没开始遍历,所有以后续出现的最优状况是 num 包含 1~n 所有的整数。r 同时表示 nums 当前的结束位置。

  • 从左往右遍历 nums ,遍历到位置 l ,此时已经包含的正整数范围是 [1, l] ,而在后续最优的情况下,nums 可能包含的正整数范围是 [1, r] ,所以当前需要的元素范围是 [l + 1, r] ,分情况讨论:

    1. nums[l] == l + 1 ,说明此时 nums 包含的正整数范围可以扩到 [1, l + 1] ,所以 l++
    2. nums[l] <= l ,此时说明 [l + 1, r] 范围上的数少了一个,所以在 nums 后续最优的情况下,可能包含的正整数范围缩小为 [1, r - 1] ,此时把 nums[r - 1] 位置上的数放到位置 l 上,下一步检查这个元素,并且 r--
    3. nums[l] > r ,与 2 同理
    4. num[num[l] - 1] == nums[l] ,在步骤 2 和 3 中没中,说明 nums[l][l + 1, r] 范围内,而且这个数应该在 nums[l] - 1 位置上,而此时出现 num[num[l] - 1] == nums[l] ,说明出现了两个 nums[l] ,既然 [l + 1, r] 上出现了重复元素,说明 [l + 1, r] 范围上少了一个数,操作同步骤 2 和 3
    5. 若步骤 2、3、4 都没中,说明发现了 [l + 1, r] 范围上的数,并且此时未发现重复,则 nums[l] 应该放在 nums[l] - 1 位置上,交换 lnums[l] - 1 位置上的元素,继续判断 l 位置上的元素
  • 最终 lr 会碰在一起,nums 已经包含的正整数范围是 [1, l] ,返回 l + 1 即可

法一代码:

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int l = 0, r = nums.size();
        while ( l < r ) {
            if ( nums[l] == l + 1 ) ++l;
            else if ( nums[l] <= l || nums[l] > r || nums[nums[l] - 1] == nums[l] ) 
                swap(nums[l], nums[--r] );
            else swap( nums[l], nums[nums[l] - 1]);
        }
        return l + 1;
    }
};
/*
时间:0ms,击败:100.00%
内存:9.4MB,击败:97.55%
*/
法二:

假设 nums 长度为 n ,理想的情况是 nums 的所有元素正好包括 1~n 并且 nums[i] == i + 1 ,这种情况我们很容易知道未出现的最小正整数是什么。

但是仅仅限于 理想 情况。虽然元素是无序的,但我们知道了什么情况下最容易找到未出现的最小正整数,我们可以向这种 理想 情况转换,具体操作:若 1 <= nums[i] <= nnums[i] != nums[nums[i] - 1] ,我们应该将其放到 nums[nums[i] - 1] 位置。假设所有 1~n 范围内的元素都放在正确的位置,剩下的就是从前往后遍历找到第一个 nums[i] != i + 1 的位置,i + 1 就是我们想要的答案。

法二代码:

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[i] != nums[nums[i] - 1] )
                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;
    }
};
/*
时间:0ms,击败:100.00%
内存:9.3MB,击败:99.55%
*/

两种方法均用到了一个关键思想 nums[i] 放到其应该待的地

上一篇:Basic Level 1077 互评成绩计算 (20分)


下一篇:分布式唯一ID解决方案-雪花算法