例题:缺失的第一个正数来源:力扣(LeetCode)
原地Hash
例题:
缺失的第一个正数
难度:hard
描述
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
示例
示例1:
输入:[1,2,0]
输出:3
示例2:
输入:[3,4,-1,1]
输出:2
示例3:
输入:[7,8,9,11,12]
输出:1
要求
你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。
分析与题解
如果不看要求的话题目是非常简单的,直接使用一个HashMap就可以了(HashSet也是一个HashMap的实例)。如下所示:
class Solution {
public int firstMissingPositive(int[] nums) {
Set<Integer> set = new HashSet<>();
//只有[1,n]范围的数据有效
for (int num: nums){
if (num>0 && num<=nums.length){
set.add(num);
}
}
//找到第一个未出现过的正数
for (int i=1; i<=nums.length; ++i){
if (!set.contains(i)){
return i;
}
}
return nums.length+1;
}
}
然而,虽然时间复杂度只有O(N),空间复杂却因为HashMap的原因随着数组的增加线性增长,也是O(N)。不满足题目的常数级别的额外空间要求。
这时候,让我们重新审题,看看能不能从哪里找到突破口:
一个未排序的数组,要求在O(N)的时间复杂度以及O(1)的空间复杂度的情况下,找到缺失的第一个正整数。
重新看这个题目,重要条件无外乎上面四个粗体字。
要求找到的这个第一个正整数就是我们的答案,而正整数不正是从1一开始的一个数组吗。如果是已经排序好了的数组,我们可以直接从1开始查找,在[1,n+1]的范围内一定可以找到缺失的正整数(数组一共只有n个数字)。但是题目明确说明了数组是未排序的,而已知的任何一个排序算法都无法做到O(N)的时间复杂度。排序再查找是不可行的!
但是我们需要排序吗?
不需要!因为我们只用找到第一个没有出现的元素就行了。因此,我们可以在这个角度考虑:常数级的空间复杂度要求我们不能创建一个新的数组。如果要对数字的顺序进行变化,就只能在原数组进行。那如何变化才能找到第一个没有出现的正整数呢?
我们继续思考给定数组与题目要求结果的联系:数组下标范围[0,n-1],题目结果[1,n+1]。
现在问题很简单了。我们是不是只需要将数组中满足题目要求的元素,放到对应下标。最后遍历一遍数组,看哪个下标没有对应的元素,不就是答案了吗?
具体代码如下
class Solution {
public int firstMissingPositive(int[] nums) {
int len = nums.length;
for (int i=0; i<len; ++i){
//这里注意两个元素交换之后,nums[i]还是没有回到应该在的位置
//比如[1,2,0],在第一次交换之后[2,1,0],2这个元素不应该在这个位置,因此需要循环直到下标为0的元素归位
while (nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]) {
swap(nums,nums[i] - 1, i);
}
}
for (int i=0; i<len; ++i){
if (nums[i]!=i+1){
return i+1;
}
}
return len+1;
}
public static void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
官方给了一个更为巧妙的解法:通过将所有负数转为len+1之后,将满足条件的元素转化为绝对值的负数。最后遍历找到第一个正数。不再需要交换元素
详情见:https://leetcode-cn.com/probl...
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
if (nums[i] <= 0) {
nums[i] = n + 1;
}
}
for (int i = 0; i < n; ++i) {
int num = Math.abs(nums[i]);
if (num <= n) {
nums[num - 1] = -Math.abs(nums[num - 1]);
}
}
for (int i = 0; i < n; ++i) {
if (nums[i] > 0) {
return i + 1;
}
}
return n + 1;
}
}
总结与反思
这题感觉并没有hard难度,因为思想其实挺简单的。无非是将键值对之间的巧妙联系起来,然而我还是学到了许多。
这几天看了遍hashmap的源码,自认为对hashmap学得挺通透了的。却被力扣的这个每日一题打了脸,稀里糊涂地做出来之后,去看题解才反应过来——噢,这原来是自己写了一个定制的hash函数啊。一直有在提醒自己不要画地为牢,却还是进入了惯性思维:以为hash就是源码那些f(x)=n&(length-1)。还是需要多做题,理解思想,而不是仅仅会学而已。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。