1. 题目解析与核心思路
LeetCode 41题"缺失的第一个正数"是一道经典的数组处理问题,题目要求找出一个未排序整数数组中缺失的最小正整数。这道题在面试中频繁出现,主要考察对数组特性的理解以及原地算法的应用能力。
1.1 问题重述
给定一个包含n个整数的未排序数组nums,找出其中没有出现的最小的正整数。算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。
示例:
- 输入:[1,2,0] → 输出:3
- 输入:[3,4,-1,1] → 输出:2
- 输入:[7,8,9,11,12] → 输出:1
1.2 关键约束分析
这道题的两个关键约束条件决定了它的难度:
- 时间复杂度O(n):排除了排序等O(nlogn)的解法
- 空间复杂度O(1):排除了使用哈希表等需要额外空间的解法
1.3 解题思路突破
解决这个问题的核心观察点是:对于一个长度为n的数组,缺失的第一个正数一定在[1, n+1]这个范围内。因为:
- 如果数组包含了1到n的所有数字,那么缺失的第一个正数就是n+1
- 否则,缺失的第一个正数一定在1到n之间
基于这个观察,我们可以将数组本身作为哈希表来使用,通过原地修改数组来标记数字是否存在。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法实现详解
2.1 哈希标记法
虽然题目要求O(1)空间复杂度,但我们可以巧妙利用原数组作为哈希表。具体步骤如下:
- 首先处理特殊情况:检查1是否存在于数组中。如果没有,直接返回1。
- 对于数组中的非正数和大于n的数,将它们替换为1(因为我们已经检查过1的存在性)。
- 使用数组索引作为哈希键:对于每个数字num,将nums[abs(num)-1]标记为负数,表示这个数字存在。
- 最后扫描数组,第一个正数对应的索引+1就是缺失的最小正数。
2.2 Python实现代码
python复制def firstMissingPositive(nums):
n = len(nums)
# 检查1是否存在
if 1 not in nums:
return 1
# 替换非正数和大于n的数为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
# 检查n是否出现
if nums[0] > 0:
return n
return n + 1
2.3 复杂度分析
- 时间复杂度:O(n),我们只进行了三次线性扫描
- 空间复杂度:O(1),除了几个临时变量外没有使用额外空间
3. 算法优化与变种
3.1 交换位置法
另一种思路是通过交换将数字放到正确的位置上。对于每个数字num,如果它在1到n的范围内,就把它交换到num-1的位置上。最后扫描数组,第一个不在正确位置上的数字就是缺失的最小正数。
python复制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
3.2 边界情况处理
需要特别注意以下几种边界情况:
- 空数组:应返回1
- 所有数字都是负数:应返回1
- 数字包含重复元素:算法需要正确处理
- 数字正好是1到n的排列:应返回n+1
4. 常见错误与调试技巧
4.1 典型错误模式
- 忽略空间复杂度限制,直接使用哈希表
- 没有正确处理重复数字的情况
- 在交换位置法中陷入无限循环(没有检查nums[nums[i]-1] != nums[i])
- 忘记处理n+1的情况
4.2 调试建议
- 先处理简单的测试用例:[1], [2], [1,1]
- 检查边界条件:[0], [-1], [100000]
- 使用print语句跟踪数组变化过程
- 特别注意索引越界问题
5. 实际应用场景
虽然这道题看起来是纯算法题,但它的核心思想在实际开发中有广泛应用:
- 数据库系统中的缺失ID检测
- 连续编号系统中的漏洞检查
- 内存管理中的空闲块查找
- 分布式系统中的序列号验证
6. 扩展练习建议
为了彻底掌握这类数组处理技巧,建议尝试以下LeetCode题目:
-
- 找到所有数组中消失的数字
-
- 数组中重复的数据
-
- 寻找重复数
-
- 错误的集合
这些题目都使用了类似的"数组本身作为哈希表"的技巧,通过对比练习可以加深理解。
7. 个人解题心得
在实际面试中遇到这道题时,建议按照以下步骤进行:
- 先明确问题要求和约束条件
- 提出暴力解法并分析其复杂度
- 思考如何优化空间复杂度
- 发现"数组索引作为哈希键"的关键点
- 处理各种边界情况
- 最后给出完整代码
我在最初解决这个问题时,最容易犯的错误是忽略了n+1的情况。后来通过大量测试用例才意识到,当数组正好包含1到n的所有数字时,缺失的数字应该是n+1。这个经验告诉我,在解决算法问题时,一定要仔细考虑所有可能的边界情况。
