Python 练习 哈希表 (困难): LeetCode 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
提示:
1 <= nums.length <= 5 * 105
-231 <= nums[i] <= 231 - 1
题解一
遍历数组,记录已出现正整数的信息
一个比较简单的思路是利用两重循环在数组nums
中查找
将最小的未出现正整数min_positive
初始化为1
,并设置store_list
数组记录数组中已遍历数据的有用信息
- 对于每个数组中的元素
element
,如果小于min_positive
,不需要更新min_positive
的值,跳过; - 如果大于
min_positive
,也不需要更新min_positive
的值,但是需要更新store_list
数组 (如果不存储store_list
信息,那遍历时无法考虑到全局信息。一个例子是nums=[2,1]
,如果采用直接遍历思路而不记录全局信息的话这个例子就跑不对); - 如果与
min_positive
相等,则最小未出现正整数的值需要更新,同时store_list
中比新的min_positive
小的值都可以删去,降低后续处理的时间复杂度,因此可以将这个算法的复杂度控制在 O(n2) 中较低的位置
class Solution(object):
def firstMissingPositive(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
min_positive = 1
store_list = []
for element in nums:
if element == min_positive:
positive = min_positive + 1
while (positive in store_list):
store_list.remove(positive)
positive += 1
min_positive = positive
elif element > min_positive:
store_list.append(element)
return min_positive
这个算法能跑通大部分 LeetCode 的测试用例,但是在极端例子下时间复杂度超过了题目要求,因为这个算法的时间复杂度是 O(n2),在大规模数组的情况下远远超过了题目要求的 O(n)
题解二:
原地哈希
要在 O(n) 时间复杂度和常数级别额外空间复杂度下找出最小的未出现正整数,可以使用原地哈希的算法。原地哈希算法在更小的时空复杂度下将题解一中提到的“已遍历数组的有用信息”存储于原数组中,避免了两重循环的使用
- 遍历数组,将每个元素放到它应该在的位置上。例如,如果数组中有元素
element
,则将element
放到数组的索引element-1
的位置上(因为数组索引是从 0 开始的); - 再次遍历数组,找到第一个不在正确位置上的元素,其索引加 1 即为未出现的最小正整数
class Solution(object):
def firstMissingPositive(self, nums):
n = len(nums)
for i in range(n):
while 1 <= nums[i] <= n and nums[nums[i] - 1] != nums[i]:
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
for i in range(n):
if nums[i] != i + 1:
return i + 1
return n + 1
题解二在时空复杂度上均有很大优势(运行时间波动很大,下面是比较好的情况)
小结:
原地哈希算法可以在 O(n) 时间复杂度和常数级别额外空间复杂度下找出数组中最小的未出现正整数,适用于问题所求结果将数组元素与数组索引相关联的数组问题。原地哈希算法通常用于内存受限或对空间效率要求高的环境中,例如嵌入式系统、低功耗设备或大规模数据处理。不过,由于就地修改可能导致数据丢失,因此在使用原地哈希算法时需要谨慎处理