一、题目介绍
题目描述
给你一个未排序的整数数组 nums,请你找出其中没有出现的最小的正整数。
进阶要求:算法的时间复杂度应当为 O(n),并且只能使用常数级别的额外空间。
示例展示
1.输入:1,2,0,输出:3
解析:1、2 均存在,缺失最小正数为 3
2.输入:3,4,-1,1,输出:2
解析:1 存在,2 未出现,为答案
3.输入:7,8,9,11,12,输出:1
解析:最小正整数 1 直接缺失
前置关键结论
数组长度为 n 时,缺失的最小正整数只存在两种可能:
1.1∼n 之间某个数字空缺(答案在区间内)
2.1∼n 全部完整存在(答案为 n+1)
负数、大于 n 的数字不会成为最终答案,可以直接忽略处理。
二、错误写法复盘(你的原始代码问题拆解)
原始代码核心错误
1.包含判断函数逻辑颠倒
自定义IsContain函数,匹配到目标值返回false,无匹配返回true,和 "判断元素是否存在" 逻辑完全相反,基础功能失效。
java
// 错误源码
public boolean IsContain(int[] nums,int a){
for (int i = 0; i < nums.length; i++) {
if (nums[i]==a) {
return false; // 找到元素本该true,写反
}
}
return true;
}
2.整体遍历逻辑设计缺陷
依靠numsi+1向后寻找空缺值,无法识别1缺失的场景,对负数、超大数值过滤失效,边界用例全部翻车。
3.时间复杂度不达标
双重循环遍历数组做存在性判断,时间复杂度 O(n2),数组长度达到105时直接超时,不满足题目硬性要求。
4.代码冗余:flag 标记、result 更新逻辑混乱,无清晰判断逻辑。
暴力修正版(仅用于理解逻辑,不推荐提交)
修复判断函数后可以正常算出结果,但无法通过大数据量用例:
java
class Solution {
public int firstMissingPositive(int[] nums) {
int result = 1;
// 先校验1是否缺失
if (!isContain(nums, result)) {
return result;
}
// 依次向后查找空缺正整数
while (true) {
result++;
if (!isContain(nums, result)) {
return result;
}
}
}
// 修正:元素存在返回true,不存在返回false
public boolean isContain(int[] nums, int a) {
for (int num : nums) {
if (num == a) return true;
}
return false;
}
}
三、最优解法:原地哈希(置换法,满足O(n)时间 +O(1)空间)
核心思想
利用数组下标作为哈希映射:
对于合法正整数 x(1≤x≤n),将它交换到数组下标 x−1 的位置。
完成一轮置换后,数组理想形态为:nums0=1、nums1=2、nums2=3......
再次遍历数组,第一个 numsi != i+1 的位置,i+1 就是缺失的最小正数;全部匹配则返回 n+1。
详细执行步骤
1.遍历数组做原地置换
只处理1~n范围内的数字,循环交换将数字放到对应下标;增加重复值判断,避免死循环。
2.线性扫描查找空缺位
从下标 0 开始对比下标与数值的对应关系,第一个错位位置即为答案。
3.全连续兜底
若数组所有位置都满足numsi=i+1,说明 1~n 全部存在,返回n+1。
Java AC 完整代码
java
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
// 原地哈希置换
for (int i = 0; i < n; i++) {
// 仅处理合法正整数,防止数组越界+重复元素死循环
while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
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;
}
}
// 1~n全部齐全
return n + 1;
}
}
案例推演演示
案例:nums = 3,4,-1,1,数组长度 n=4
1.置换过程
i=0,nums 0=3,交换到下标 2 → -1,4,3,1
i=1,nums 1=4,交换到下标 3 → -1,1,3,4,继续交换 nums 1=1 到下标 0 → 1,-1,3,4
i=2,nums 2=3,位置正确无需交换
i=3,nums 3=4,位置正确无需交换
最终数组:1,-1,3,4
2.遍历校验
i=0:1=0+1 ✔️
i=1:-1≠2,直接返回结果 2,和样例输出一致。
四、复杂度分析
•时间复杂度 O(n):每个元素最多被交换 1 次,两次线性遍历数组,无嵌套循环
•空间复杂度 O(1):全程原地修改数组,仅使用临时交换变量,无额外数组、集合开辟
五、易错踩坑总结
1.置换循环必须增加numsnums\[i-1] != numsi判断,数组存在重复数字时会陷入无限循环;
2.负数、大于数组长度的数字直接跳过置换,不会作为答案,无需处理;
3.不要忽略全连续场景,例如1,2,3最终需要返回 4;
4.暴力枚举解法逻辑简单,但大数据用例直接超时,面试必须掌握原地哈希解法。
六、适用场景拓展
该原地哈希思想可以拓展到:
1.寻找数组重复元素
2.寻找缺失元素类线性空间限制题目
是算法面试高频的空间优化经典技巧。