继续每日一题
今天给大家带来一道将数组视为哈希表的算法
题目描述:
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
题目示例:
由于题目要求我们「只能使用常数级别的空间」,而要找的数一定在 [1, N + 1] 左闭右闭(这里 N 是数组的长度)这个区间里。因此,我们可以就把原始的数组当做哈希表来使用。事实上,哈希表其实本身也是一个数组;
最小正整数从1开始,那么没有出现的最小正整数一定在1到n+1之间(因为如果1到n都出现了,那么答案就是n+1)
所以我们可以只考虑数组中的1到n之间的数,负数、0和大于n的数可以忽略(因为它们不影响1到n之间的最小缺失正整数)。
考虑到对时间和空间复杂度的要求,我们可以采用原地置换,具体流程通过文字描述不太直观,下面通过一个case给大家描述一下:
数组 [3,4,-1,1]
-
i=0,nums[0]=3,它应该在位置2(即下标2),交换nums[0]和nums[2]:得到[-1,4,3,1]
然后i=0,此时nums[0]=-1,不在1到4范围内,跳过。 -
i=1,nums[1]=4,它应该在位置3,交换nums[1]和nums[3]:得到[-1,1,3,4]
然后i=1,此时nums[1]=1,它应该在位置0,但是位置0是-1,交换:得到[1,-1,3,4]
然后i=1,现在nums[1]=-1,跳过。 -
i=2,nums[2]=3,在位置2(正确),跳过。
-
i=3,nums[3]=4,在位置3(正确),跳过。
然后遍历数组
- i=0,num[0]=1(正确);
- i=1,num[1]=-1,但实际上i=1,对应的数是2,所以返回i+1
但是在置换过程中可能会存在当前位置和要置换的位置的数值是相同的,导致死循环,所以在置换之前还需要比较两个值是否相同
核心逻辑如下:
- 当前元素:currentVal=nums[i]
- 当前元素需要在的位置:index=currentVal - 1,因为数组下标从0开始,所以需要减一
- 要置换的元素:nums[index]
while (true) {
int currentVal = nums[i];
//当前元素需要置换到该位置,数组下标从0开始,所以需要减一
int changeIndex = currentVal - 1;
int needChangeVal = nums[changeIndex];
if (currentVal < 1 || currentVal > n) {
//不在有效范围无需置换
break;
}
int needChangeVal = nums[changeIndex];
if (currentVal == needChangeVal) {
//两个数字相等,无需置换
break;
}
nums[i] = needChangeVal;
nums[changeIndex] = currentVal;
}
完整代码如下:
public static int firstMissingPositive(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; i++) {
while (true) {
int currentVal = nums[i];
//当前元素需要置换到该位置,数组下标从0开始,所以需要减一
int changeIndex = currentVal - 1;
if (currentVal < 1 || currentVal > n) {
//不在有效范围无需置换
break;
}
int needChangeVal = nums[changeIndex];
if (currentVal == needChangeVal) {
//两个数字相等,无需置换
break;
}
nums[i] = needChangeVal;
nums[changeIndex] = currentVal;
}
}
for (int i = 0; i < n; i++) {
int currentVal = nums[i];
if (currentVal != i + 1) {
return i + 1;
}
}
//如果都存在则返回 n+1
return n + 1;
}