题目
题目来源:力扣(LeetCode)
给你一个未排序的整数数组 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 存到下标 0 的位置,把元素 2 存在下标 1 的位置,就是把元素 x 存到下标 x-1 位置,也就是让当前的元素放到正确的位置(所谓正确的位置就是数组[1, 2, 3, 4, 5, …. n]),可以看出: 位置 = 元素 - 1。
然后扫描这个数组,看看元素 1 是不是在下标为 0 的位置,元素 2 是不是在下标为 1 的位置,找到第一个 违反规则的位置,即找到第一个没有存放正确数字的位置, 那么该位置上的数字就是缺失的第一个正数
这个思想就相当于我们自己编写哈希函数,这个哈希函数的规则特别简单,那就是数值为 i 的数映射到下标为 i - 1 的位置。
在扫描数组的过程中:
- 如果当前元素已经在正确的位置,就continue剪枝。
如果当前元素不在正确的位置,那就把当前的元素放到它该放的位置trueIndex,前提是这个元素要放置的位置在数组允许的索引范围内,再满足这个前提下,有两种可能:
- 发现这个该放的位置trueIndex上的元素放置了一个正确的元素,那就不要再放了。
- 如果trueIndex上不是正确的元素,就把这个元素与当前访问位置上的元素做交换,交换完只保证了trueIndex是正确元素,当前访问的位置上不一定是正确元素,于是需要循环执行第2步。
最后数组的样子就是 元素大小 在 数组索引范围+1 内的元素都被放到了正确的位置上。
- 此时从头遍历这个数组,找出缺失的第一个正数,两种情况:
- 第一个 元素大小 不等于 索引+1 的元素。
- 元素都在各自的正确位置上,那就返回数组长度+1
/**
* @param {number[]} nums
* @return {number}
*/
var firstMissingPositive = function (nums) {
const len = nums.length;
for (let i = 0; i < len; ++i) {
// 如果当前元素已经在正确的位置,就continue剪枝。
if (nums[i] == i + 1) continue;
//这里用while是因为交换回来的值不一定是"值配其位"的,那么就可能还需要进行交换
//比如[4,1,2,3]
// 1.第一次将4与3交换,形成[3,1,2,4],明显3是"值不配位"的,也满足条件,进入新的交换
// 2.第二次将3与2交换,形成[2,1,3,4],明显2也是"值不配位"的,且满足条件,进入新的交换
// 3.第三次将2与1交换,形成[1,2,3,4],值全都配位了,后面的while也就都不会进了
// (此处也说明while循环不会每一次都把数组里面的所有元素都看一遍。如果有一些元素在这一次的循环中
// 被交换到了它们应该在的位置,那么在后续的遍历中,由于它们已经在正确的位置上了,代码再执行到它们
// 的时候,就会被跳过。----引用自@liweiwei1419的题解)
//**所以此处不能用if**
while (nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]) {
//将出现的值都交换到相应的位置上,组成一个近似排序的数组
//其中的负数和大于数组长度的值都可以不用管,因为它是作为一个出口存在的
//这里值1放在索引0上,即值(i)需要放在索引(i-1)上
// const temp = nums[nums[i] - 1];
// nums[nums[i] - 1] = nums[i];
// nums[i] = temp;
let ind = nums[i] - 1;
[nums[i], nums[ind]] = [nums[ind], nums[i]];
}
}
for (let i = 0; i < len; ++i) {
//第一个不满足 值(nums[i])=索引(i+1) 的值所在索引(i)+1即最小未出现的正整数
//注意:以下的例子都是经过换位之后
//例1:[1,2,3,4] 这个例子是完全排序的数组,且每个位置都正确,那么就应该返回数组长度+1,而不是3+1
//例2:[1,-1,3,4] 其中-1是第一个也是唯一一个"值不配位"的值,那么它所占的位置就是未出现的最小正整数
//例3:[5,11,7,9] 这个例子是完全没有进入while排序的,因为他们所有的值都"值不配位",那么同上面的例2,
// 第一个"值不配位"的值所占的位置就是未出现的最小正整数
if (nums[i] != i + 1) {
return i + 1;
}
}
// 都正确则返回数组长度 + 1
return len + 1;
}