官方给的题解我自己感觉有点难理解,按照以下思路能更好理解些。
缺失的第一个整数
给你一个未排序的整数数组 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 <= 105
-231 <= nums[i] <= 231 - 1
什么是原地哈希?
原地哈希(In-place Hashing)是一种在有限空间内处理数据的算法技术,通常用于在不使用额外存储空间的情况下对数据进行哈希操作。这种方法在内存受限的环境中非常有用,例如嵌入式系统或实时数据处理。
核心思想
原地哈希的核心思想是通过修改原始数据本身来实现哈希操作,而不是创建新的数据结构来存储哈希结果。这样可以节省内存空间,但可能会改变原始数据。
应用场景
-
去重:在数组中去除重复元素。
-
查找:快速查找特定元素。
-
排序:通过哈希值对数据进行排序。
实现方式
-
数组索引作为哈希键:利用数组的索引作为哈希键,将元素值映射到对应位置。
-
交换元素:通过交换元素位置,将元素放置到其哈希值对应的索引处。
示例
假设有一个数组 [4, 3, 2, 7, 8, 2, 3, 1]
,我们希望去除重复元素并保留唯一元素。
-
初始化:遍历数组,使用数组索引作为哈希键。
-
哈希映射:将每个元素映射到其值对应的索引位置。
-
去重:如果发现重复元素,则跳过或标记。
最终,数组可能变为 [1, 2, 3, 4, 7, 8]
,去除了重复元素。
优点
-
节省空间:不需要额外存储空间。
-
高效:通常具有较高的时间效率。
缺点
-
数据修改:原始数据会被改变。
-
复杂度:实现可能较为复杂,需处理冲突和边界情况。
代码
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
// 将每个正整数放到正确的位置上
for (int i = 0; i < n; i++) {
// 当 nums[i] 在 [1, n] 范围内,并且 nums[i] 不在正确的位置上时,进行交换
while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
swap(nums, i, nums[i] - 1);
}
}
// 找到第一个不满足 nums[i] == i + 1 的位置
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
// 如果所有位置都满足条件,则返回 n + 1
return n + 1;
}
// 交换数组中两个元素的位置
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
代码思路
-
核心思想:
-
将数组中的每个正整数
nums[i]
放到它应该在的位置上,即nums[i]
应该放在索引nums[i] - 1
处。 -
最终遍历数组,找到第一个不满足
nums[i] == i + 1
的位置,即为缺失的最小正整数。
-
-
具体步骤:
-
遍历数组:对于每个元素
nums[i]
,如果它在[1, n]
范围内(n
是数组长度),并且它不在正确的位置上(即nums[nums[i] - 1] != nums[i]
),则将其交换到正确的位置。 -
查找缺失的正整数:遍历数组,找到第一个不满足
nums[i] == i + 1
的位置,返回i + 1
。如果所有位置都满足条件,则返回n + 1
。
-
-
交换操作:
-
通过
swap
函数交换数组中的两个元素。
-
代码分析
-
时间复杂度:
-
虽然代码中有嵌套的
while
循环,但每个元素最多只会被交换一次,因此总的时间复杂度为 O(n)。
-
-
空间复杂度:
-
只使用了常数级别的额外空间(交换时的临时变量),因此空间复杂度为 O(1)。
-
-
原地修改:
-
代码直接修改了原始数组
nums
,没有使用额外的数据结构,符合原地哈希的思想。
-
-
边界条件:
-
如果数组为空,返回
1
。 -
如果数组包含所有正整数
[1, 2, ..., n]
,则返回n + 1
。
-