41. First Missing Positive 缺失的第一个正数

Title

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

哈希表

Solve

我们可以将数组所有的数放入哈希表,随后从1开始依次枚举正整数,并判断其是否在哈希表中。

如果数组的长度为N,那么第一种做法的时间复杂度为O(N),空间复杂度为O(N)。

我们为什么要使用哈希表?

因为哈希表是一个可以支持快速查找的数据结构:给定一个元素,可以在O(1)的时间查找该元素是否在哈希表中。

因此,我们可以考虑将给定的数组设计成哈希表的【替代产品】。

实际上,对于长度为N的数组,其中没有出现的最小正整数只能在[1, N+1]中,这样以来,我们将所有在[1, N]范围内的数放入哈希表,也可以得到最终的答案,而给定的数组恰好长度为N,这让我们有了一种将数组设计成哈希表的思路:

我们对数组进行遍历,对于遍历到的数 x,如果它在 [1, N] 的范围内,那么就将数组中的第 x-1 个位置(注意:数组下标从 0 开始)打上「标记」。在遍历结束之后,如果所有的位置都被打上了标记,那么答案是 N+1,否则答案是最小的没有打上标记的位置加 1。

由于数组中的数没有任何限制,因此这并不是一件容易的事情。但我们可以继续利用上面的提到的性质:由于我们只在意 [1, N] 中的数,因此我们可以先对数组进行遍历,把不在 [1, N] 范围内的数修改成任意一个大于 N 的数(例如 N+1)。这样一来,数组中的所有数就都是正数了,因此我们就可以将「标记」表示为「负号」。算法的流程如下:

  1. 我们将数组中所有小于等于 0 的数修改为 N+1;
  2. 我们遍历数组中的每一个数 x,它可能已经被打了标记,因此原本对应的数为 |x|,其中 | | 为绝对值符号。如果 ∣x∣∈[1,N],那么我们给数组中的第 ∣x∣−1 个位置的数添加一个负号。注意如果它已经有负号,不需要重复添加;
  3. 在遍历完成之后,如果数组中的每一个数都是负数,那么答案是 N+1,否则答案是第一个正数的位置加 1。

在这里插入图片描述

Code

	def firstMissingPositive(self, nums: List[int]) -> int:
		length = len(nums)
		for i in range(length):
			if nums[i] <= 0:
				nums[i] = length + 1
		for i in range(length):
			num = abs(nums[i])
			if num <= length:
				nums[num - 1] = -abs(nums[num - 1])
		for i in range(length):
			if nums[i] > 0:
				return i + 1
		return length + 1

复杂度分析

时间复杂度:O(N),其中 N 是数组的长度。

空间复杂度:O(1)。

置换

Solve

将给定的数组「恢复」成下面的形式:如果数组中包含 x∈[1,N],那么恢复后,数组的第 x - 1 个元素为 x。

在恢复后,数组应当有[1, 2, …, N]的形式,但其中有若干个位置上的数是错误的,每一个错误的位置就代表了一个缺失的正数。

以题目中的示例二 [3, 4, -1, 1] 为例,恢复后的数组应当为 [1, -1, 3, 4],我们就可以知道缺失的数为 2。

那么我们如何将数组进行恢复呢?我们可以对数组进行一次遍历,对于遍历到的数x=nums[i],如果 x∈[1,N],我们就知道 x 应当出现在数组中的 x - 1 的位置,因此交换nums[i] 和 nums[x−1],这样 x 就出现在了正确的位置。

在完成交换后,新的 nums[i] 可能还在 [1,N] 的范围内,我们需要继续进行交换操作,直到 x ∉ [1,N]。

注意到上面的方法可能会陷入死循环。如果 nums[i] 恰好与 nums[x−1] 相等,那么就会无限交换下去。

因此在这种情况下,x 本就已经出现在了正确的位置,只是数组里面有多个 x 而已。因此我们就不需要进行交换,而是开始遍历下一个数。

由于每次的交换操作都会使得某一个数交换到正确的位置,因此交换的次数最多为 N,整个方法的时间复杂度为 O(N)。

Code

    def firstMissingPositive(self, nums: List[int]) -> int:
        length = len(nums)
        for i in range(length):
            while 1 <= nums[i] <= length and nums[nums[i] - 1] != nums[i]:
                nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
        for i in range(length):
            if nums[i] != i + 1:
                return i + 1
        return length + 1

复杂度分析

时间复杂度:O(N),其中 N 是数组的长度。

空间复杂度:O(1)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章