文章目录
41. 缺失的第一个正数
题目来源
题目分析
给你一个未排序的整数数组
nums
,找出其中没有出现的最小的正整数。
题目难度
- 难度:困难
题目标签
- 标签:数组、哈希表
题目限制
1 <= nums.length <= 5 * 10^5
-2^31 <= nums[i] <= 2^31 - 1
解题思路
思路1:数组记录法
- 问题定义:
- 需要找到数组
nums
中没有出现的最小的正整数。
- 需要找到数组
- 核心算法:
- 创建一个长度为
n+1
的数组a
,用于记录1
到n
之间每个数字是否出现。 - 遍历
nums
,将出现过的正整数标记在数组a
中。 - 再次遍历数组
a
,找到第一个未标记的位置,即为缺失的最小正整数。
- 创建一个长度为
思路2:哈希表思想
- 问题定义:
- 利用原数组
nums
作为哈希表,通过交换元素的方式,将每个数字放到其对应的索引位置上。
- 利用原数组
- 核心算法:
- 遍历数组,找到第一个索引位置上的数字不等于索引加一的元素,即为缺失的最小正整数。
核心算法步骤
方法1:数组记录法
- 初始化一个长度为
n+1
的数组a
。 - 遍历
nums
,标记出现过的正整数。 - 遍历数组
a
,找到第一个未标记的位置。
方法2:哈希表思想
- 遍历
nums
,将每个数字放到其对应的索引位置上。 - 遍历数组,找到第一个索引位置上的数字不等于索引加一的元素。
代码实现
以下是两种方法的 Java 代码实现:
public int firstMissingPositive(int[] nums) {
int n = nums.length;
int[] a = new int[n + 1];
for (int num : nums) {
if (num > 0 && num <= n) {
a[num] = 1;
}
}
for (int i = 1; i <= n; i++) {
if (a[i] == 0) {
return i;
}
}
return n + 1;
}
public int firstMissingPositive2(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; i++) {
while (nums[i] > 0 && 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;
}
}
return n + 1;
}
代码解读
- 方法1:通过创建一个额外数组来记录每个数字是否出现。
- 方法2:通过在原数组上交换元素,将每个数字放到其正确的位置上。
性能分析
- 时间复杂度:
- 方法1:O(n),因为我们需要遍历两次数组。
- 方法2:O(n),尽管内部有一个 while 循环,但每个数字最多只会被交换一次。
- 空间复杂度:
- 方法1:O(n),我们需要一个长度为
n+1
的额外数组。 - 方法2:O(1),我们只使用了常数级别的额外空间。
- 方法1:O(n),我们需要一个长度为
测试用例
public static void main(String[] args) {
int[] nums = {3, 4, -1, 1};
System.out.println(firstMissingPositive(nums)); // 输出: 2
System.out.println(firstMissingPositive2(nums)); // 输出: 2
int[] nums2 = {1, 2, 0};
System.out.println(firstMissingPositive(nums2)); // 输出: 3
System.out.println(firstMissingPositive2(nums2)); // 输出: 3
}
扩展讨论
有没有其他方法?
- 排序法:首先对数组进行排序,然后遍历排序后的数组,找到第一个不满足
nums[i] == i + 1
的位置。这种方法的时间复杂度取决于排序算法,通常是O(n log n)
。
原地哈希法
- 说明:方法2实际上是一种原地哈希的方法,它通过交换元素来将数字放置到其对应的索引位置上,从而避免了使用额外的空间。
总结
本题通过两种不同的方法来寻找缺失的第一个正数。方法1使用了一个额外的数组来记录出现过的数字,而方法2则利用了原地哈希的思想,通过交换元素来达到目的。两种方法各有优劣,方法1在空间上占用更多,但实现简单;方法2在空间上更优,但实现上稍微复杂一些。在实际应用中,可以根据题目对时间和空间的要求来选择合适的方法。