算法 - 缺失的第一个正数
1. 题目
给你一个未排序的整数数组 nums,请你找出其中没有出现的最小的正整数。
要求:时间复杂度 O(n),并且只使用常数级别额外空间。
示例:
1 | 输入: nums = [3,4,-1,1] |
2. 解题思路
关键结论:长度为 n 的数组,答案一定落在 [1, n + 1] 区间内(最理想情况数组恰好是 1..n,则答案为 n+1)。
2.1 原地哈希(把 i 放到位置 i-1)
目标:让数组「归位」——值 x(1 ≤ x ≤ n)应放到下标 x - 1。
遍历数组,对每个
nums[i],只要它在[1, n]且「它该去的位置上的值还不等于它」,就交换过去(循环,因为换回来的值可能也需归位)。归位后再扫一遍,第一个
nums[i] !== i + 1的位置,i + 1就是缺失的最小正数。时间复杂度:O(n)(每个元素最多被交换归位一次),空间复杂度:O(1)。
2.2 为什么不能用哈希集合?
可以用 Set 存所有正数再从 1 开始找,满足 O(n) 时间,但空间 O(n),不满足「常数空间」的进阶要求——面试先讲 Set 思路,再优化到原地哈希。
3. TypeScript 实现
3.1 原地哈希(交换法)
1 | function firstMissingPositive(nums: number[]): number { |
坑点:解构交换
[nums[i], nums[target]] = [...]中target必须先算好;若写成nums[nums[i]-1]内联,赋值左侧求值顺序会导致索引错乱。
3.2 标记法(取负,等价思路)
先把所有 ≤0 的数替换成 n+1(移出有效区),再对每个值的绝对值 v(1≤v≤n)把 nums[v-1] 取负作为「出现过」标记,最后第一个非负下标 +1 即答案。同样 O(n) 时间 O(1) 空间。
4. 面试延伸
- 原地哈希 vs 排序:排序 O(n log n),原地哈希利用「答案范围有界」把空间压到 O(1),是本题精髓。
- 变形题「数组中重复的数字 / 找丢失的数字」都能复用「下标当哈希表」这一模式。
- 追问:为什么 while 而不是 if?因为一次交换换来的新值可能也需要归位,必须持续下沉。
难度:困难 | LeetCode 41 | 原地哈希经典