在问 · 算法题解

41. 缺失的第一个正数

困难哈希表原地置换归位力扣原题 ↗

题目描述

输入一个没有排序的整数数组 nums。

返回没有在 nums 中出现的最小正整数。

要求时间复杂度为 O(n),额外空间为常数级。

示例 1
输入:nums = [1,2,0]
输出:3

解释:1 和 2 都在,3 不在。

示例 2
输入:nums = [3,4,-1,1]
输出:2

解释:1 在,2 不在。

示例 3
输入:nums = [7,8,9,11,12]
输出:1

解释:最小的正整数 1 就不在数组中。

数据范围

  • 1 <= nums.length <= 10^5
  • -2^31 <= nums[i] <= 2^31 - 1

解题思路:原地置换归位

把值 x 放到下标 x-1 的位置,反复交换直到当前位置稳定。随后第一个 nums[i] != i+1 的位置给出答案。

为什么正确

处理交换后,所有位于有效范围的已归位数字 x 都满足 nums[x-1]=x。

复杂度分析

时间 O(n),空间 O(1)。

易错点

交换时必须处理重复值,否则两个相同数字会导致死循环。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def firstMissingPositive(self, nums):
        n=len(nums); i=0
        while i<n:
            x=nums[i]
            if 1<=x<=n and nums[x-1]!=x: nums[i],nums[x-1]=nums[x-1],nums[i]
            else: i+=1
        for i,x in enumerate(nums):
            if x!=i+1: return i+1
        return n+1

载入编辑器,自己跑一遍 →

其他解法

哈希集合最直观但占 O(n) 空间;原地负号标记和置换归位都把数组本身当作哈希表,达到 O(1) 额外空间,前者用符号做标记,后者把数放回对应位置。

解法 2:原地负号标记 时间 O(n),空间 O(1)

答案一定在 [1, n+1] 内。先把所有非正数改成 n+1,使数组只剩正数。再遍历每个数的绝对值 x,若 x ≤ n,就把 nums[x-1] 改成负数,表示 x 出现过。最后第一个仍为正数的下标 i 给出答案 i+1,全部为负则答案是 n+1。

和置换归位一样满足 O(n) 时间、O(1) 额外空间。它用符号位充当哈希表,不需要处理交换循环,正确性更容易论证;缺点是会破坏原数组的符号。

class Solution:
    def firstMissingPositive(self, nums):
        n = len(nums)
        for i in range(n):
            if nums[i] <= 0:
                nums[i] = n + 1
        for i in range(n):
            x = abs(nums[i])
            if x <= n:
                nums[x - 1] = -abs(nums[x - 1])
        for i in range(n):
            if nums[i] > 0:
                return i + 1
        return n + 1

解法 3:哈希集合 时间 O(n),空间 O(n)

把所有数放入哈希集合。从 1 开始依次查询,第一个不在集合中的正整数就是答案。由于集合最多 n 个元素,最多查询 n+1 次。

思路最直观,但需要 O(n) 额外空间,不满足题目的常数空间要求。适合作为思路起点和对拍基准。

class Solution:
    def firstMissingPositive(self, nums):
        seen = set(nums)
        k = 1
        while k in seen:
            k += 1
        return k

哈希表 专题的其他题目

查看 哈希表 全部题目 →