先看题目。
这题是hard难度,实际上就是奇技淫巧。没做过这道题,你想破头都想不出来时间复杂度O(n)且空间复杂度O(1)的做法。
思路如下:
对于一个长度为n的nums的数组,缺失的第一个正数绝对是在[1,n+1]之间产生。如果从1到n都在数组中出现了,答案就是n+1。否则就是[1,n]的第一个没出现的数字。所以说,我们现在需要统计1到n是否在数组出现。我们可以用hashset去做这个事,遍历这个数组,将处于[1,n]之间的数字放到hashset中。遍历结束后,从1开始查,看哪个数字没出现,就是第一个缺失的正整数。
然而,这题要求是O(1)的额外空间,我们无法另外创建一个hashset。我们只能另辟蹊径,把这个数组本身当作一个hashset去用。我们的目的是要用这个数组来判断[1,n]是否出现过,而nums数组长度是n,那我们可以用nums[0]判断1是否出现过,nums[1]判断2是否出现过,以此类推,nums[n-1]判断n是否出现过。
那我们如何通过nums[n-1]去判断n是否出现过呢,这又是另外一个奇技淫巧——如果n出现过,我们把nums[n-1]改成负数。当然,这么做的前提是数组里没有0和负数,不然负负变正了。我们先得提前把数组里的负数和0改成n+1。为啥要n+1呢,因为如果是改成[1,n]里的数,比如1,那我们就没法判断原来数组中1是否真的出现过。
进行完改负数的操作后,我们再遍历一波数组,我们碰上的第一个正数,其所在的下标+1(数组第几位),即是第一个缺失的正整数。如果一个正数都没碰上,说明[1,n]都出现过,那结果就是n+1了。
代码如下:
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
for(size_t i = 0;i < nums.size();++i){
//把0和负数都改成n+1
if(nums[i] <= 0){
nums[i] = nums.size()+1;
}
}
for(size_t i = 0;i < nums.size();++i){
//因为我们会把nums[i]原有的数值改成负数,所以我们使用abs(nums[i])获得原数值
int val = abs(nums[i]);
//如果val在[1,n]里,并且它还没被标记过,那我们就去标记它
if(val <= nums.size() && nums[val-1] > 0){
nums[val-1] = -nums[val-1];
}
}
//再次遍历,第一个正数的下标+1,就是答案
for(size_t i = 0;i < nums.size();++i){
if(nums[i] > 0){
return i+1;
}
}
//如果没正数,说明[1,n]都齐了,那就是n+1是答案
return nums.size()+1;
}
};