LeetCode 热题 HOT 100(普通数组)41. 缺失的第一个正数

100 阅读5分钟

题目描述

41. 缺失的第一个正数

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

请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

 

示例 1:

输入: nums = [1,2,0]
输出: 3
解释: 范围 [1,2] 中的数字都在数组中。

示例 2:

输入: nums = [3,4,-1,1]
输出: 2
解释: 1 在数组中,但 2 没有。

示例 3:

输入: nums = [7,8,9,11,12]
输出: 1
解释: 最小的正数 1 没有出现。

 

提示:

  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1

题目思路 Self

  • 使用一个 map 记录所有 num 存在情况
  • 从 1 开始判断 map 中是否存在,递增以找到第一个未出现的正数
func firstMissingPositive(nums []int) int {
    minPositiveNum := 1
    exists := make(map[int]bool)
    for _, num := range nums {
        exists[num] = true
    }
    for exists[minPositiveNum] {
        minPositiveNum++
    }
    return minPositiveNum
}

Claude 思路

  1. 对于一个长度为 n 的数组,缺失的最小正整数一定在 [1, n+1] 范围内。
  2. 我们可以将数组本身作为哈希表来标记已经出现的正数。
  3. 具体做法是:对于数组中的每个元素 x,如果 1 ≤ x ≤ n,则将 nums[x-1] 标记为负数。
  4. 最后遍历数组,第一个正数的位置 i+1 就是缺失的最小正整数。

代码实现

func firstMissingPositive(nums []int) int {
    n := len(nums)

    // 第一步:将数组中小于 0 的数修改为 n+1
    // 因为我们只关心 1 到 n 之间的正整数
    // 数组大小为 n,因此缺失的正整数只能位于 [1, n+1] 之间
    // [2, 3, ..., n] 缺失 1
    // [1, 2, 3, ..., n] 缺失 n+1
    for i := 0; i < n; i++ {
        if nums[i] <= 0 {
            nums[i] = n+1
        }
    }

    // 第二部:将出现的正数位置对应位置的数标记为负数
    // 使用数组索引模拟哈希表,num 对应的数组索引为 num-1
    for i := 0; i < n; i++ {
        num := abs(nums[i])
        if num <= n {
            // 将对应位置的值变为负数,表示这个位置的数已经出现
            // 需要使用绝对值防止重复标记导致正负抵消
            nums[num-1] = -abs(nums[num-1])
        }
    }

    // 第三步:找到第一个正数的位置
    for i := 0; i < n; i++ {
        if nums[i] > 0 {
            // 位置 i 对应的数是 i+1,所以第一个正数的位置对应的值 i+1 就是缺失的第一个正整数
            return i + 1
        }
    }

    // 如果数组中包含了 1 到 n 的所有正整数,那么缺失的最小正整数就是n+1
    return n+1
}

func abs(a int) int {
    if a < 0 {
        return -a
    }
    return a
}

关键数据变化表

以示例2 nums = [3,4,-1,1] 为例,展示算法执行过程中的数据变化:

第一步:将数组中<=0的数替换为n+1(这里n=4)

索引原始值处理后说明
033无需修改
144无需修改
2-15替换为n+1=5
311无需修改

第一步处理后,数组变为:[3,4,5,1]

第二步:标记已存在的正整数

索引当前值需标记的位置处理后说明
03nums[2][3,4,-5,1]标记位置3-1=2的元素为负
14nums[3][3,4,-5,-1]标记位置4-1=3的元素为负
2-5-[3,4,-5,-1]已经是负数,表示绝对值为5的数存在,但5>n,超出范围,忽略
3-1nums[0][-3,4,-5,-1]标记位置1-1=0的元素为负

第二步处理后,数组变为:[-3,4,-5,-1]

第三步:寻找第一个正数的位置

索引是否为正数说明
0-3表示位置0+1=1存在于原数组
14表示位置1+1=2不存在于原数组
2-5表示位置2+1=3存在于原数组
3-1表示位置3+1=4存在于原数组

在索引1处找到了正数,表示位置1+1=2的数不存在于原数组,因此缺失的最小正整数是2。

考察知识点

这道题主要考察以下知识点:

  1. 原地哈希算法:利用数组本身作为哈希表,避免使用额外空间。

  2. 索引与值的映射关系:使用数组索引和值之间的关系来存储信息。

  3. 时间和空间复杂度优化:在 O(n) 时间和 O(1) 空间复杂度下解决问题。

  4. 数组元素标记技巧:使用正负号作为标记手段,而不改变元素的绝对值。

  5. 问题转化能力:将"找缺失的最小正整数"转化为"找第一个未被标记的位置"。

  6. 边界条件处理

    • 处理非正数
    • 处理超出数组长度范围的数
    • 处理重复元素
  7. 代码简洁性:在保持算法复杂度的前提下,使用清晰简洁的代码实现复杂的逻辑。

  8. 分析能力:理解缺失的最小正整数一定在 [1, n+1] 范围内。

这道题是一个典型的"原地哈希"问题,考察了如何在有限的空间内高效地解决问题。这种技巧在处理需要标记元素出现情况而又不允许使用额外空间的场景中非常有用。