这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战
题目
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
示例 1:
输入:nums = [1,2,0]
输出:3
示例 2:
输入:nums = [3,4,-1,1]
输出:2
示例 3:
输入:nums = [7,8,9,11,12]
输出:1
提示:
1 <= nums.length <= 5 * 105
-231 <= nums[i] <= 231 - 1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/first-missing-positive
题目给的额外补充:
- Think about how you would solve the problem in non-constant space. Can you apply that logic to the existing space?
- We don't care about duplicates or non-positive integers
- Remember that O(2n) = O(n)
方法签名
public static int firstMissingPositive(int[] nums) {
}
解析
首先是O(n),我们不能排序之后再顺序找一次了,或者说我们的算法就规定在了必须使用若干次顺序遍历的方式来进行。
其次,要求我们使用常数级别的额外空间,我们也用不了HashMap或者类似的字典来记录了,或者说我们只能用确定数量的变量进行记录。
那么,我们该怎么办?
按照最简单的思路,我们当然是希望:
- 读一次数组,就知道信息了
- 记录已经读取过的信息
实际上我们知道:
- 对于给定的数组,假设长度为n,那么从1开始出现的连续正整数至多到n。(1)
- 并且对于题目给定的数组,并不要求在执行方法前后是相同的,也就是说我们可以在这个数组上做修改。
那么不妨将给定的数组这样子做:
- 使用输入的数组,记录我们读取的结果。
为什么可以这么做?
-
其实原因很简单:
- 从(1)可以推知:如果我们按顺序记录数,那么在记录完毕后,我们通过读取记录就可以知道,丢失的第一个正整数要么在(0,n),要么就是n+1(数组中包含了所有的数)
- 需要注意的是,我们并不关心那些是负数的值,实际上对于那些值>n的,我们也不关心(因为最后我们只需要知道在(1,n)这个区间里,哪个数没有出现过)
那么我们按照如下的思路,来记录读取的结果:
- 我们顺序从前往后读,将读出来的数放到对应的格子里:
记读出来的数为a,数组为arr,a对应的索引为i,我们交换arr[a]和arr[i],并在此时停下来,下一次继续检查i的位置,避免遗漏
如果在数组范围之外,我们就给一个特殊的值,代表我们不关心,减少我们下一次判断时的成本;
如果我们读完了整个数,还出现这个特殊的值的话,就意味着这个索引对应的数字没有出现在数组之中。
- 此外,为了防止相同的数导致的死循环,我们在交换的时候,需要比较一下是否相同。
代码
public static int firstMissingPositive(int[] nums) {
System.out.println("");
for (int i = 0; i < nums.length; i++) {
if(nums[i]<=0) continue;
if(nums[i]-1 >= nums.length){
nums[i] = 0;
continue;
}
if(nums[i]-1 == i) continue;
int tmp = nums[i] == nums[nums[i]-1]?0:nums[nums[i]-1];
nums[nums[i]-1] = nums[i];
nums[i] = tmp;
i--;
}
int idx;
for (idx = 0; idx < nums.length; idx++) {
if(nums[idx]-1!=idx) return idx+1;
}
return idx+1;
}
\