【LeetCode-数组】缺失的第一个正数
题目描述
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
示例:
输入: [1,2,0]
输出: 3
输入: [3,4,-1,1]
输出: 2
输入: [7,8,9,11,12]
输出: 1
说明: 你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。
题目链接: https://leetcode-cn.com/problems/first-missing-positive/
做这一题之前,可以先做一下找到所有数组中消失的数字(思路2)。
思路1
使用哈希表来做,题目要求常数级别的额外空间,所以不能额外定义哈希表,但我们可以就在输入数组上构建哈希表。遍历数组,假设当前的元素为 nums[i],由于是找出 [1, n] 之间未出现的最小整数,所以,我们将 nums[abs(nums[i])-1] 设为负数,使用 abs(nums[i]) 的原因是因为 nums[i] 可能在之前的遍历过程中以及被设为负数了,但下标是一个大于等于 0 的数字,所以需要取绝对值。例如,假设 nums[1] = 3,nums[2] = 6,那么遍历到 nums[1] 时,我们将 nums[2] 设为 -6(nums[nums[1]-1] = -nums[nums[1]-1]),代表 2+1=3 出现过。当遍历结束时,我们再遍历一遍 nums,如果 nums[i]>=0 的话,说明 i+1 没有在 nums 中出现,返回 i+1。
还有一个问题,就是输入的 nums 中可能包含负数。比如,nums 中包含 -1 但不包含 1,那么我们将 nums[abs(-1)-1] 设为了负数,表明 1 出现了,但实际上 1 并没有出现。这个问题的解决办法是:我们首先对输出数组 nums 遍历一遍,将其中的负数变为 0,然后在将 nums[abs(nums[i])-1] 设为负数的过程中,如果 nums[abs(nums[i])-1]==0,我们就将 nums[abs(nums[i])-1] 设为 -(num.size()+1),这样的话,在后序的过程中,nums[abs(nums[i])-1] 不会对其他的元素产生影响。具体代码如下:
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
for(int i=0; i<nums.size(); i++){ // 将负数转为0
if(nums[i]<0) nums[i]=0;
}
for(int i=0; i<nums.size(); i++){ // 将nums[abs(nums[i])-1]设为相反数
int idx = abs(nums[i])-1;
if(idx>=0 && idx<nums.size()){
if(nums[idx]==0) nums[idx]=-nums.size()-1;
else nums[idx] = -abs(nums[idx]); //这样可以保证一个数nums[i]出现多次时,nums[abs(nums[i])-1]一直为负数
}
}
for(int i=0; i<nums.size(); i++){
if(nums[i]>=0) return i+1;
}
return nums.size()+1;
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(1)
还可以这样写:
先将小于等于 0 的元素转为数组中出现的任意一个正数,然后求解。代码如下:
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
if(nums.empty()) return 1;
int t = 0; // 任意的一个正数
for(int i=0; i<nums.size(); i++){
if(nums[i]>0){
t = nums[i];
break;
}
}
if(t<=0) return 1;
for(int i=0; i<nums.size(); i++){
if(nums[i]<=0) nums[i]=t; // 将小于等于0的数转为t
}
for(int i=0; i<nums.size(); i++){
if(abs(nums[i])-1>=0 && abs(nums[i])-1<nums.size()){
nums[abs(nums[i])-1] = -abs(nums[abs(nums[i])-1]);
}
}
for(int i=0; i<nums.size(); i++){
if(nums[i]>=0) return i+1;
}
return nums.size()+1;
}
};
这种写法个人感觉更容易理解,时间复杂度也是 O(n)。
- 时间复杂度:O(n)
- 空间复杂度:O(1)
思路2
遍历数组,假设当前的数字 nums[i],记为 x:
- 如果 nums[i]==i+1,则将 i++;
- 否则,如果 x>=1 && x<=nums.size(),则我们将 x 放到 nums[x-1] 的位置上。举个例子,假设数组为 [3,1,2],假设当前的数字为第一个数字 3,也就是 x = 3,则我们将 3 放到第 2 个位置(位置从 0 开始计数),则一次交换后数组为 [2,3,1];更详细的例子如下
图片来自这篇题解。
交换完成后,我们遍历数组,假设当前下标为 i,如果 i+1!=nums[i],则返回 i+1;如果对于数组的每一个下标都有 i+1==nums[i],则返回 nums.size()+1,例如数组为 [1,2,3],则没有出现的最小正整数为 nums.size()+1=3+1=4。
代码如下:
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
if(nums.empty()) return 1;
int i = 0;
while(i<nums.size()){
if(i+1==nums[i]) i++; // 当前数字已经放到了正确的位置上,处理下一个数字,i++
else{
int x = nums[i];
if(x>=1 && x<=nums.size() && x!=nums[x-1]){ // 注意x!=nums[x-1],防止[1,1]的情况下出现死循环
swap(nums[i], nums[nums[i]-1]); // 不能用x,要用nums[i]
}
else i++; // 当前位置的数字超过数组下标范围,不能交换,则处理下一个数字,i++
}
}
for(int i=0; i<nums.size(); i++){
if(nums[i]!=i+1) return i+1;
}
return nums.size()+1; // 注意,返回nums.size()+1
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(n)
相关题目
1、找到所有数组中消失的数字:https://www.cnblogs.com/flix/p/12857693.html