本文旨在记录我的解题思路与思考过程(部分解法参考了其他优秀题解,参考来源已附文末),作为个人学习笔记以便后续复习回顾。文中如有疏漏或不足之处,欢迎指正交流。
一. 题述:
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。请你实现时间复杂度为 O(n)
并且只使用常数级别额外空间的解决方案。
二. 解题思路:
2.1 理解题意:
如果没有要求空间复杂限制在O(1)的话,这道题利用哈希表很容易写出来。
在这里我想要解释一下为什么利用哈希表可以很快解决这道题。
因为哈希表(Hash Table)是一种非常高效的数据结构,它可以在平均情况下以 O(1) 的时间复杂度进行增删改查操作(空间换时间,空间复杂度O(n))。所以当我们需要频繁地检查某个正整数是否存在于数组中。哈希表的查找操作是 O(1) 的,因此可以快速判断一个数是否存在。
由于哈希表方法解题不符合空间复杂度O(1)的要求,所以我们可以考虑将给定的数组设计成哈希表的「替代产品」。
实际上,对于一个长度为 N 的数组,其中没有出现的最小正整数只能在 [1,N+1] 中。这是因为如果 [1,N] 都出现了,那么答案是 N+1,否则答案是 [1,N] 中没有出现的最小正整数。这样一来,我们将所有在 [1,N] 范围内的数放入哈希表,也可以得到最终的答案。而给定的数组恰好长度为 N,这让我们有了一种将数组设计成哈希表的思路:
我们对数组进行遍历,对于遍历到的数 x,如果它在 [1,N] 的范围内,那么就将数组中的第 x−1 个位置(注意:数组下标从 0 开始)打上「标记」。在遍历结束之后,如果所有的位置都被打上了标记,那么答案是 N+1,否则答案是最小的没有打上标记的位置加 1。
那么如何设计这个「标记」呢?由于数组中的数没有任何限制,因此这并不是一件容易的事情。但我们可以继续利用上面的提到的性质:由于我们只在意 [1,N] 中的数,因此我们可以先对数组进行遍历,把不在 [1,N] 范围内的数修改成1(这里涉及一个问题就是要提前判断1在不在数组里,如果数组里没有1那么直接返回1)。这样一来,数组中的所有数就都是正数了,因此我们就可以将「标记」表示为「负号」(打标记过程中要提前加上绝对值防止出现两次标记后将其变为正号的情况)。
2.2 解题步骤:
算法的流程如下:
- 遍历数组中所有的数,查看是否存在 1;
- 我们将数组中所有小于 1 以及 大于 N 的数 1;
- 我们遍历数组中的每一个数 x,它可能已经被打了标记,因此原本对应的数为 ∣x∣,其中 ∣∣ 为绝对值符号。打标记过程中要提前加上绝对值防止出现两次标记后将其变为正号的情况。
- 在遍历完成之后,如果数组中的每一个数都是负数,那么答案是 N+1,否则答案是第一个正数的位置加 1。
三. 解题算法:
3.1 哈希表方法(不满足题意要求)
3.1.1 基于Java编写
import java.util.HashSet;
public class Test {
public int hashMethod(int[] nums) {
// 创建哈希表
HashSet<Integer> numSet = new HashSet<>();
for (int num: nums) {
numSet.add(num);
}
System.out.println(numSet);
int N = nums.length;
for (int i = 1; i <= N; i++) {
if (!numSet.contains(i)) {
return i;
}
}
return N + 1;
}
3.1.2 基于Python编写
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
# 创建一个哈希表
hash_set = set()
# 将所有元素装入哈希表
for num in nums:
hash_set.add(num)
N = len(nums)
# 遍历1-N
for i in range(1, N + 1):
if i not in hash_set:
return i
return N + 1
3.2 类似哈希表方法(满足题意)
3.2.1 基于Java编写
class Solution {
public int firstMissingPositive(int[] nums) {
int N = nums.length;
// 处理空数组的情况
if (N == 0) {
return 1;
}
// 判断是否出现了 1
int a = -1;
for (int i = 0; i < N; i++) {
// 若nums中出现 1 则将 a 变为 1
if (nums[i] == 1) {
a = 1;
}
}
if (a == -1) {
return 1;
}
// 遍历数组, 修正超出边界[1, N]的值
for (int i = 0; i < N; i++) {
if (nums[i] < 1 || nums[i] > N) {
nums[i] = 1;
}
}
// 打标记
for (int i = 0; i < N; i++) {
// 获取索引且保证索引为正
int index = Math.abs(nums[i]) - 1;
nums[index] = -Math.abs(nums[index]);
}
// 寻找nums中第一个非负元素
for (int i = 0; i < N; i++) {
if (nums[i] > 0) {
return i + 1;
}
}
return N + 1;
}
}
3.2.2 基于Python编写
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
N = len(nums)
# 数组为空的情况
if N == 0:
return 1
# 判断数组中是否存在 1
a = 0
for i in range(N):
if nums[i] == 1:
a = 1
if a == 0:
return 1
# 将数组中的数缩放到[1, N]
for i in range(N):
if nums[i] < 1 or nums[i] > N:
nums[i] = 1
# 打标记
for i in range(N):
index = abs(nums[i]) - 1
nums[index] = - abs(nums[index])
for i in range(N):
if nums[i] > 0:
return i + 1
return N + 1
版权声明
本文部分解题思路和代码实现参考自 LeetCode 官方题解《 41.缺失的第一个正数 》(https://leetcode.cn/problems/first-missing-positive/solutions/304743/que-shi-de-di-yi-ge-zheng-shu-by-leetcode-solution/),所有引用内容仅供学习交流使用。