☰
LeetCode41题解:O(1)空间找缺失最小正数
2026/10/27 7:43:57 网站建设 项目流程

1. 问题背景与核心挑战

缺失的第一个正数(First Missing Positive)是LeetCode上一道经典的数组处理题目,编号41,属于热题100中的困难级别。这道题看似简单,却暗藏玄机——它要求我们在时间复杂度O(n)且空间复杂度O(1)的条件下,找出一个未排序整数数组中最小的缺失正整数。

举个例子,给定数组[3,4,-1,1],缺失的第一个正数是2;而[7,8,9,11,12]的答案则是1。这个问题的难点在于严格的时空复杂度限制,排除了排序、哈希表等常规解法,需要我们对数组本身进行"就地"操作。

提示:这道题在亚马逊、微软等大厂面试中频繁出现,因为它能有效考察候选人对数组索引和值之间关系的理解,以及空间复杂度的优化能力。

2. 暴力解法与优化思路

2.1 直观解法及其缺陷

最直观的解法是先将数组排序,然后遍历查找缺失的正数。例如:

def firstMissingPositive(nums): nums.sort() res = 1 for num in nums: if num == res: res += 1 return res

这种方法虽然简单,但排序的时间复杂度是O(nlogn),不满足题目要求。另一种思路是使用哈希表存储所有数字,然后从1开始检查是否存在:

def firstMissingPositive(nums): num_set = set(nums) for i in range(1, len(nums)+2): if i not in num_set: return i

这种方法虽然时间复杂度是O(n),但空间复杂度也是O(n),同样不符合要求。

2.2 关键突破点:利用数组索引作为哈希

真正的解法需要利用数组本身作为哈希表。核心观察是:对于一个长度为n的数组,缺失的第一个正数一定在[1, n+1]范围内。如果数组包含了1到n的所有数字,那么缺失的就是n+1;否则缺失的一定在1到n之间。

我们可以通过以下步骤实现:

  1. 首先检查1是否存在于数组中,如果不存在直接返回1
  2. 将所有非正数和大于n的数替换为1(因为这些数不影响结果)
  3. 使用数组索引作为哈希键,通过改变符号来标记数字是否存在
  4. 最后遍历数组,找到第一个正数对应的索引

3. 最优解法的详细实现

3.1 算法步骤拆解

让我们用[3,4,-1,1]这个例子来逐步说明:

  1. 检查1是否存在:存在,继续
  2. 替换无效数字:将-1替换为1,数组变为[3,4,1,1]
  3. 使用索引标记:
    • 取绝对值:3 → 将索引3-1=2处的值变为负数:[3,4,-1,1]
    • 4 → 索引3(超出范围,忽略)
    • 1 → 索引0:[3,4,-1,1](已经是负数)
    • 1 → 同上
  4. 最终数组:[3,4,-1,1]
  5. 遍历查找第一个正数:索引1的值为4,所以缺失的是2

3.2 Python实现代码

def firstMissingPositive(nums): n = len(nums) # 检查1是否存在 if 1 not in nums: return 1 # 替换无效数字 for i in range(n): if nums[i] <= 0 or nums[i] > n: nums[i] = 1 # 使用索引作为哈希键 for i in range(n): num = abs(nums[i]) if num == n: nums[0] = -abs(nums[0]) else: nums[num] = -abs(nums[num]) # 查找第一个正数 for i in range(1, n): if nums[i] > 0: return i if nums[0] > 0: return n return n + 1

3.3 复杂度分析

  • 时间复杂度:O(n),我们只进行了三次线性遍历
  • 空间复杂度:O(1),除了输入数组外没有使用额外空间

4. 边界条件与常见错误

4.1 特殊输入情况

  1. 空数组:应返回1
  2. 全负数数组:应返回1
  3. 连续数组[1,2,3,...,n]:应返回n+1
  4. 重复数字数组:如[1,1,2,2]应返回3

4.2 常见实现错误

  1. 忽略1的检查:直接开始标记会导致错误
  2. 处理n时的特殊逻辑:需要单独处理,因为索引n超出范围
  3. 重复标记问题:应该取绝对值后再标记,避免重复操作改变符号
  4. 索引越界:当数字等于数组长度时,需要特殊处理

注意:在面试中,很多候选人会在处理数组长度等于n的数字时出错,这是需要特别注意的边界情况。

5. 算法优化与变种

5.1 交换法替代标记法

除了符号标记法,还可以使用交换法:将每个数字交换到它应该在的位置(即nums[i]应该放在nums[nums[i]-1])。这种方法同样满足O(n)时间和O(1)空间的要求:

def firstMissingPositive(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

5.2 相关变种题目

  1. 找出所有缺失的数字(LeetCode 448)
  2. 找出重复的数字(LeetCode 287)
  3. 第一个缺失的非负数
  4. 缺失的第一个质数

6. 面试技巧与实战建议

6.1 面试回答策略

  1. 先说明暴力解法及其局限性
  2. 提出哈希表思路,指出空间复杂度问题
  3. 引出利用数组本身作为哈希表的想法
  4. 详细解释标记法的实现步骤
  5. 讨论边界条件和优化空间

6.2 调试技巧

  1. 使用小规模测试用例(如[1], [2], [1,1])
  2. 打印中间结果,观察标记过程
  3. 特别注意n的处理和索引边界
  4. 使用断言验证关键步骤

6.3 性能优化

虽然算法已经是理论最优,但在实际实现中还可以:

  1. 合并某些遍历步骤
  2. 使用位运算替代符号标记(如果允许修改数据)
  3. 提前终止条件:如果在标记过程中发现1缺失可以直接返回

7. 实际应用场景

这道算法题虽然抽象,但其核心思想在实际开发中有广泛应用:

  1. 数据库系统中的缺失ID检测
  2. 内存管理中的空闲块标记
  3. 图像处理中的像素值重映射
  4. 数据压缩算法中的值域优化

理解这种"就地哈希"的技术,可以帮助我们在资源受限的环境下(如嵌入式系统)设计出更高效的算法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询