当前位置 博文首页 > 苏学算法的博客:【LeetCode】41. 缺失的第一个正数

    苏学算法的博客:【LeetCode】41. 缺失的第一个正数

    作者:[db:作者] 时间:2021-09-04 21:24

    一、题目描述

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

    示例 1:

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

    示例 2:

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

    示例 3:

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

    提示:
    你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。

    二、 解题思路 & 代码

    将数组视为哈希表

    最早知道这个思路是在《剑指 Offe》这本书上看到的,感兴趣的朋友不妨做一下这道问题:剑指 Offer 03. 数组中重复的数字。下面简要叙述:

    1. 由于题目要求我们「只能使用常数级别的空间」,而要找的数一定在 [1, N + 1] 左闭右闭(这里 N 是数组的长度)这个区间里。因此,我们可以就把原始的数组当做哈希表来使用。事实上,哈希表其实本身也是一个数组
    2. 我们要找的数就在 [1, N + 1] 里,最后 N + 1 这个元素我们不用找。因为在前面的 N 个元素都找不到的情况下,我们才返回 N + 1;
    3. 那么,我们可以采取这样的思路:就把 1 这个数放到下标为 0 的位置, 2 这个数放到下标为 1 的位置,按照这种思路整理一遍数组。然后我们再遍历一次数组,第 1 个遇到的它的值不等于下标的那个数,就是我们要找的缺失的第一个正数。
    4. 这个思想就相当于我们自己编写哈希函数这个哈希函数的规则特别简单,那就是数值为 i 的数映射到下标为 i - 1 的位置

    在这里插入图片描述

    from typing import List
    
    
    class Solution:
    
        # 3 应该放在索引为 2 的地方
        # 4 应该放在索引为 3 的地方
    
        def firstMissingPositive(self, nums: List[int]) -> int:
            size = len(nums)
            for i in range(size):
                # 先判断这个数字是不是索引,然后判断这个数字是不是放在了正确的地方
                while 1 <= nums[i] <= size and nums[i] != nums[nums[i] - 1]:
                    self.__swap(nums, i, nums[i] - 1)
    
            for i in range(size):
                if i + 1 != nums[i]:
                    return i + 1
    
            return size + 1
    
        def __swap(self, nums, index1, index2):
            nums[index1], nums[index2] = nums[index2], nums[index1]
    

    说明:Python 里可以这样写 n u m s [ n u m s [ i ] ? 1 ] , n u m s [ i ] = n u m s [ i ] , n u m s [ n u m s [ i ] ? 1 ] nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1] nums[nums[i]?1],nums[i]=nums[i],nums[nums[i]?1] ,但是这里赋值有先后顺序,写成 n u m s [ i ] , n u m s [ n u m s [ i ] ? 1 ] = n u m s [ n u m s [ i ] ? 1 ] , n u m s [ i ] nums[i], nums[nums[i] - 1] = nums[nums[i] - 1], nums[i] nums[i],nums[nums[i]?1]=nums[nums[i]?1],nums[i], 就会出错。建议封装成单独的函数,避免出错。

    复杂度分析:

    1. 时间复杂度 O ( N ) O(N) O(N),这里 N N N 是数组的长度。
      1)说明:while 循环不会每一次都把数组里面的所有元素都看一遍。如果有一些元素在这一次的循环中被交换到了它们应该在的位置,那么在后续的遍历中,由于它们已经在正确的位置上了,代码再执行到它们的时候,就会被跳过。
      2)最极端的一种情况是,在第 1 个位置经过这个 while 就把所有的元素都看了一遍,这个所有的元素都被放置在它们应该在的位置,那么 for 循环后面的部分的 while 的循环体都不会被执行。
      3)平均下来,每个数只需要看一次就可以了,while 循环体被执行很多次的情况不会每次都发生。这样的复杂度分析的方法叫做均摊复杂度分析
      4)最后再遍历了一次数组,最坏情况下要把数组里的所有的数都看一遍,因此时间复杂度是 O(N)。

    2. 空间复杂度:O(1)。

    参考:

    1. LeetCode题解
    cs