力扣 41. 缺失的第一个正数 有意思
41. 缺失的第一个正数
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。
O(n)
并且只使用常数级别额外空间的解决方案。
示例 1:
输入:nums = [1,2,0]
输出:3
示例 2:
输入:nums = [3,4,-1,1]
输出:2
提示:
1 <= nums.length <= 5 * 105
-231 <= nums[i] <= 231 - 1
法一:标记
对于一个数组,长度为N
,没有出现的最小正整数只能在[1,N+1]
中
这是因为如果 [1, N]
都出现了,那么答案是,否则答案是
[1, N]
中没有出现的最小正整数。
先将所以不大于0的元素都赋值为N+1,接下来只处理正整数;
将值在[1,N]的元素放入标记数组中来记录,因为此标记数组长度N和原数组长度N一样,所以可以合并(原数组既保存数组信息,同时也标记哪些值已经出现过):
遍历数组,当前元素num,如果 num∈[1,N],在数组上做标记,将num-1(下标0开始)的元素赋值为其原值的绝对值的负,
如nums=[3,1,5],遍历到num=1,第2个元素的值是1,nums[1-1]=num[0]=-3,-3的符号表示此元素下标(0)已经被标记出现过,即有元素的值为0+1=1,原数组里面已经出现过1;
遍历打标记完成后,再次遍历寻找不为负数的元素,那么这个元素的下标+1就是还没有出现过的最小正整数,如果找不到,表示1到N都出现过,那么答案为N+1
查看代码
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n=nums.size();
//小于0的修改为N+1
for(int& num:nums){
if(num<=0){
num=n+1;
}
}
//打标记
for(int i=0;i<n;i++){
int num=abs(nums[i]);
if(num<=n){
nums[num-1]=-abs(nums[num-1]);//当前位置的前一个位置标记在数组中
}
}
for(int i=0;i<n;i++){
if(nums[i]>0){
return i+1;
}
}
return n+1;
}
};
法二:置换
除了打标记以外,我们还可以使用置换的方法,将给定的数组「恢复」成下面的形式:
如果数组中包含 x∈[1,N],那么恢复后,数组的第x−1 个元素为 x。
在恢复后,数组应当有 [1, 2, ..., N] 的形式,但其中有若干个位置上的数是错误的,每一个错误的位置就代表了一个缺失的正数。以题目中的示例二 [3, 4, -1, 1] 为例,恢复后的数组应当为 [1, -1, 3, 4],我们就可以知道缺失的数为 2。
那么我们如何将数组进行恢复呢?我们可以对数组进行一次遍历,对于遍历到的数 x=nums[i],如果 x∈[1,N],我们就知道 x 应当出现在数组中的 x−1 的位置,因此交换 nums[i] 和 nums[x−1],这样 x 就出现在了正确的位置。在完成交换后nums[i] 可能还在 [1, N]的范围内,我们需要继续进行交换操作,直到 x不属于[1,N]。
注意到上面的方法可能会陷入死循环。如果nums[i] 恰好与 nums[x−1] 相等,那么就会无限交换下去。此时我们有nums[i]=x=nums[x−1],说明 x 已经出现在了正确的位置。因此我们可以跳出循环,开始遍历下一个数。
查看代码
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n=nums.size();
for(int i=0;i<n;i++){
while(nums[i]>0&&nums[i]<n&&nums[nums[i]-1]!=nums[i]){
swap(nums[i],nums[nums[i]-1]);
}
}
for(int i=0;i<n;i++){
if(nums[i]!=i+1)
return i+1;
}
return n+1;
}
};