题目回顾
给定一个未排序的整数数组 nums,找出没有出现的最小正整数。要求:
- 时间复杂度:O(n)
- 空间复杂度:O(1)(只能使用常数级别的额外空间)
示例:
[1,2,0]→ 3(1和2都在,下一个就是3)[3,4,-1,1]→ 2(1在,但2缺失)[7,8,9,11,12]→ 1(1就直接缺失)
核心思想:把数组当成"房子"
这道题最巧妙的地方在于:我们直接把原数组当成哈希表来用,不额外占空间。
关键观察:
- 数组长度是
n,那么答案一定在[1, n+1]这个范围内- 最小的正整数最大不会超过
n+1 - 如果
1~n都在,答案就是n+1
- 最小的正整数最大不会超过
核心策略:让每个数字"回到自己的家"
想象数组的每个位置是一栋房子,门牌号从 0 到 n-1。数字 i 的"家"应该在门牌号 i-1 的位置(因为数组是0索引)。
游戏规则:
只有数字 i 在范围 [1, n] 内,它才有资格住进自己的家。
详细步骤(三步走)
第一步:忽略"不合格"的数字
遍历数组,我们只关心 1~n 范围内的数字。负数、0、大于n的数都是"黑户",暂时不管它们。
第二步:让数字"各回各家"
再次遍历,对于每个数字 nums[i]:
- 如果
1 ≤ nums[i] ≤ n,它就应该住在索引nums[i]-1的位置 - 一直交换,直到:
- 数字已经在正确位置了(
nums[i] == i+1) - 或者要交换的位置已经是对的数字了(避免死循环)
- 数字已经在正确位置了(
这个过程就像:
你走进一个乱糟糟的走廊,看到门牌3号房门口站着数字5,你就说:"喂,5,你的家在4号房!" 然后你把5送到4号房门口,结果4号房门口是数字2,你继续送2回家...直到所有人都站对位置为止。
第三步:找出第一个"空房子"
再遍历一次数组,找到第一个数值不等于索引+1的位置:
nums[i] ≠ i+1→ 答案就是i+1
如果所有位置都对,答案就是 n+1。
代码实现(Java)
public class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
// 第二步:让数字"各回各家"
for (int i = 0; i < n; i++) {
// 当nums[i]在[1,n]范围内且不在正确位置时,持续交换
while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
// 交换nums[i]和nums[nums[i] - 1]
int temp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = temp;
}
}
// 第三步:找出第一个"空房子"
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) {
return i + 1; // 这个位置该住i+1,但没住对
}
}
// 都住对了,答案就是n+1
return n + 1;
}
}
示例演示:nums = [3,4,-1,1]
数组长度 n = 4,我们关注数字 1~4。
初始状态:
索引: 0 1 2 3
数值: [3, 4, -1, 1]
第二步过程:
-
i=0: nums[0]=3,应该在索引2 → 交换位置0和2 索引: 0 1 2 3 数值: [-1, 4, 3, 1] nums[0]现在=-1(不合格),继续下一个i
-
i=1: nums[1]=4,应该在索引3 → 交换位置1和3 索引: 0 1 2 3 数值: [-1, 1, 3, 4]
-
i=1(继续): nums[1]=1,应该在索引0 → 交换位置1和0 索引: 0 1 2 3 数值: [1, -1, 3, 4]
-
i=2: nums[2]=3,已在正确位置(索引2)
-
i=3: nums[3]=4,已在正确位置(索引3)
最终状态:
索引: 0 1 2 3
数值: [1, -1, 3, 4]
第三步检查:
- 索引0: nums[0]=1 ✓
- 索引1: nums[1]=-1 ≠ 2 → 答案是 2
复杂度分析
-
时间复杂度 O(n):
- 每次交换至少把一个数字放到正确位置
- 最多进行 n 次交换(每次交换固定一个数字)
- 所以总时间是线性的
-
空间复杂度 O(1):
- 只使用了几个临时变量
- 没有使用额外的数组或数据结构
为什么这个方法有效?
关键性质: 当我们完成第二步后,对于所有在 [1,n] 范围内的数,如果它存在,它一定在正确的位置上。
换句话说:如果数字 k(1≤k≤n)存在于数组中,那么 nums[k-1] 一定等于 k。
因此,当我们发现 nums[i] ≠ i+1 时,就意味着数字 i+1 根本不存在于数组中!
总结
这道题的精髓在于原地哈希:
- 空间换时间 → 用数组本身当哈希表
- 数字归位 → 每个数字都有"命中注定"的位置
- 一次扫描 → 线性时间找到答案
就像整理书架:每本书都有固定编号的位置,我们只要把书放回正确位置,然后扫一眼就能知道哪本缺失了。
希望这篇文章能帮助你理解这道经典题目!