LeetCode41——缺失的第一个正数
题目描述
给一个未排序的整数数组,找出其中没有出现的最小正整数。
示例:
输入:[1,2,0]
输出:3
要求:时间复杂度为 \(O(N)\),只能使用常数个额外空间。
题解
抛去时间复杂度和空间复杂度要求的话,最容易想到的方法是先对数组进行排序,然后挨个遍历就能找到没有出现的最小正整数,这样的时间复杂度为 \(O(NlogN + N)\)。
但是本题对时间复杂度和空间复杂度做了要求。假设先不考虑空间复杂度,想要将 \(O(NlogN + N)\) 的时间复杂度降为 \(O(N)\),就意味着不能对原数组进行常规的排序处理,这时就可以想到使用类似于计数排序的方法来降低时间复杂度,也可以使用 HashSet 来辅助,这样就能将时间复杂度成功降到 \(O(N)\)。再来考虑如何降低线性空间复杂度到常数级别,无论对于计数排序还是使用HashSet来说,其实都应用了哈希表的思想,用到哈希表,那就意味着必然要使用到额外的线性空间,想要将线性的额外空间降低到常数级别,也就意味着需要我们自己去编写哈希规则,或者说,实现 原地哈希。
回归到题中,要找数组中未出现的最小正整数。假设数组长度为 \(N\),要找的目标值 \(target\) 一定在 \([1,N+1]\) 中,证明这一点的方法也很简单,假设目标值不在 \([1,N+1]\) 中,首先,数组所有元素就必然互不相等且在 \([1,N+1]\) 之间,这样 \(target\) 也必然会在 \([1,N+1]\) 中,推出矛盾,说明目标值一定在 \([1,N+1]\) 区间里。
那么按照这样的思路:将 \(1\) 这个数字放到数组索引为 \(0\) 的位置上,\(2\) 放到数组索引为 \(1\) 的位置上,以此类推,将所有在 \([1,N+1]\) 区间内的元素放到相应的位置,然后再遍历一遍数组,第一个值不等于数组索引位置 + 1 的元素即为要找的目标值。
代码如下:
class Solution {
public int firstMissingPositive(int[] nums) {
if(nums.length == 0){
return 1;
}
for(int i = 0; i < nums.length;i++){
while(nums[i] > 0 && nums[i] <= nums.length && nums[nums[i] - 1] != nums[i] ){
//注意这里的nums[nums[i]-1] != nums[i] 不可以写成 nums[i] != i + 1
//因为nums[nums[i]-1] != nums[i]表示数组里下标为 nums[i] - 1 的数值不等于下标为 i 的数值。
//更通俗点说,就是判断当前元素是否已经被放到了正确位置,没有则交换,若已经放到了正确位置,则不进行交换
//而 nums[i] != i + 1 根本无法判断当前元素是否已经通过交换放到了正确位置
//举个反例[1,1],若用 nums[i] != i + 1 做判断条件会死循环
swap(nums,nums[i] - 1,i);
}
}
for(int i = 0;i < nums.length;i++){
if(nums[i] != i + 1){
return i + 1;
}
}
return nums.length + 1;
}
public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
代码中虽然使用了两层循环嵌套,但是其实内层循环中,每次交换都会有一个数字回归到相应位置,因此最坏情况下时间复杂度也只是 \(O(N)\)。