Hard
题目描述
给你一个未排序的整数数组 nums,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的算法。
示例 1:
输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。
示例 2:
输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。
示例 3:
输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正整数 1 没有出现。
提示:
1 <= nums.length <= 10^5-2^31 <= nums[i] <= 2^31 - 1
解题思路
这是一个经典的数组问题,需要在 O(n) 时间和 O(1) 空间复杂度下解决。
核心思路: 对于长度为 n 的数组,第一个缺失的正数一定在范围 [1, n+1] 内。我们可以利用数组本身作为哈希表来标记数字的存在性。
算法步骤:
- 预处理:将所有非正数和大于 n 的数替换为一个超出范围的值(如 n+1)
- 标记存在性:遍历数组,对于每个数字 x,将 nums[|x|-1] 标记为负数(如果还不是负数的话)
- 查找结果:再次遍历数组,第一个正数的位置 i 对应的答案就是 i+1
关键技巧:
- 使用负号来标记数字的存在,避免使用额外空间
- 处理边界情况:负数、0、超出范围的数
时间复杂度: O(n) - 需要遍历数组3次 空间复杂度: O(1) - 只使用常数级别的额外空间
这种方法巧妙地利用了数组索引和值的对应关系,是解决此类问题的经典技巧。
代码实现
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
// 第一步:将所有非正数和大于n的数替换为n+1
for (int i = 0; i < n; i++) {
if (nums[i] <= 0 || nums[i] > n) {
nums[i] = n + 1;
}
}
// 第二步:使用负号标记存在的数字
for (int i = 0; i < n; i++) {
int num = abs(nums[i]);
if (num <= n) {
nums[num - 1] = -abs(nums[num - 1]);
}
}
// 第三步:找到第一个正数的位置
for (int i = 0; i < n; i++) {
if (nums[i] > 0) {
return i + 1;
}
}
return n + 1;
}
};
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)
# 第一步:将所有非正数和大于n的数替换为n+1
for i in range(n):
if nums[i] <= 0 or nums[i] > n:
nums[i] = n + 1
# 第二步:使用负号标记存在的数字
for i in range(n):
num = abs(nums[i])
if num <= n:
nums[num - 1] = -abs(nums[num - 1])
# 第三步:找到第一个正数的位置
for i in range(n):
if nums[i] > 0:
return i + 1
return n + 1
public class Solution {
public int FirstMissingPositive(int[] nums) {
int n = nums.Length;
// 第一步:将所有非正数和大于n的数替换为n+1
for (int i = 0; i < n; i++) {
if (nums[i] <= 0 || nums[i] > n) {
nums[i] = n + 1;
}
}
// 第二步:使用负号标记存在的数字
for (int i = 0; i < n; i++) {
int num = Math.Abs(nums[i]);
if (num <= n) {
nums[num - 1] = -Math.Abs(nums[num - 1]);
}
}
// 第三步:找到第一个正数的位置
for (int i = 0; i < n; i++) {
if (nums[i] > 0) {
return i + 1;
}
}
return n + 1;
}
}
/**
* @param {number[]} nums
* @return {number}
*/
var firstMissingPositive = function(nums) {
const n = nums.length;
// 第一步:将所有非正数和大于n的数替换为n+1
for (let i = 0; i < n; i++) {
if (nums[i] <= 0 || nums[i] > n) {
nums[i] = n + 1;
}
}
// 第二步:使用负号标记存在的数字
for (let i = 0; i < n; i++) {
const num = Math.abs(nums[i]);
if (num <= n) {
nums[num - 1] = -Math.abs(nums[num - 1]);
}
}
// 第三步:找到第一个正数的位置
for (let i = 0; i < n; i++) {
if (nums[i] > 0) {
return i + 1;
}
}
return n + 1;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 需要遍历数组3次,每次都是O(n) |
| 空间复杂度 | O(1) | 只使用常数级别的额外空间,原地修改数组 |
相关题目
. Missing Number (Easy)
. Find the Duplicate Number (Medium)
. Couples Holding Hands (Hard)
. Smallest Number in Infinite Set (Medium)
. Maximum Number of Integers to Choose From a Range I (Medium)
. Smallest Missing Non-negative Integer After Operations (Medium)
. Maximum Number of Integers to Choose From a Range II (Medium)
. Smallest Missing Integer Greater Than Sequential Prefix Sum (Easy)