1.缺失的第一个正数
要求时间复杂度应为O(n),空间复杂度为常数。
方法一:哈希表(空间复杂度不符合要求)
按照刚才我们读例子的思路,其实我们只需从最小的正整数 11 开始,依次判断 22、 33 、44 直到数组的长度 N 是否在数组中。
如果当前考虑的数不在这个数组中,我们就找到了这个缺失的最小正整数。
由于我们需要依次判断某一个正整数是否在这个数组里,我们可以先把这个数组中所有的元素放进哈希表。接下来再遍历的时候,就可以以 O(1)O(1) 的时间复杂度判断某个正整数是否在这个数组。
由于题目要求我们只能使用常数级别的空间,而哈希表的大小与数组的长度是线性相关的,因此空间复杂度不符合题目要求。
参考代码 1:
Java import java.util.HashSet; import java.util.Set; public class Solution { public int firstMissingPositive(int[] nums) { int len = nums.length; Set<Integer> hashSet = new HashSet<>(); for (int num : nums) { hashSet.add(num); } for (int i = 1; i <= len ; i++) { if (!hashSet.contains(i)){ return i; } } return len + 1; } }
复杂度分析:
时间复杂度:O(N),这里 N 表示数组的长度。第 1 次遍历了数组,第 2 次遍历了区间 [1, len] 里的元素。
空间复杂度:O(N),把 N个数存在哈希表里面,使用了 N 个空间。
方法二:二分查找(时间复杂度不符合要求)
根据刚才的分析,这个问题其实就是要我们查找一个元素,而查找一个元素,如果是在有序数组中查找,会快一些;
因此我们可以将数组先排序,再使用二分查找法从最小的正整数 11 开始查找,找不到就返回这个正整数;
这个思路需要先对数组排序,而排序使用的时间复杂度是 O(N log N)O(NlogN),是不符合这个问题的时间复杂度要求。
参考代码 2:
Java import java.util.Arrays; public class Solution { public int firstMissingPositive(int[] nums) { int len = nums.length; Arrays.sort(nums); for (int i = 1; i <= len; i++) { int res = binarySearch(nums, i); if (res == -1) { return i; } } return len + 1; } private int binarySearch(int[] nums, int target) { int left = 0; int right = nums.length - 1; while (left <= right) { int mid = (left + right) >>> 1; if (nums[mid] == target) { return mid; } else if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; } }
复杂度分析:
时间复杂度:O(N log N),这里 NN 表示数组的长度。时间复杂度主要消耗在排序上,排序使用 O(Nlog N)O(NlogN)。二分查找使用 O(log N)O(logN);
空间复杂度:O(1)。
方法三:将数组视为哈希表
由于题目要求我们“只能使用常数级别的空间”,而要找的数一定在 [1, N + 1] 左闭右闭(这里 N 是数组的长度)这个区间里。因此,我们可以就把原始的数组当做哈希表来使用。事实上,哈希表其实本身也是一个数组;
我们要找的数就在 [1, N + 1] 里,最后 N + 1 这个元素我们不用找。因为在前面的 N 个元素都找不到的情况下,我们才返回 N + 1;
那么,我们可以采取这样的思路:就把 11 这个数放到下标为 00 的位置, 22 这个数放到下标为 11 的位置,按照这种思路整理一遍数组。然后我们再遍历一次数组,第 11 个遇到的它的值不等于下标的那个数,就是我们要找的缺失的第一个正数。
这个思想就相当于我们自己编写哈希函数,这个哈希函数的规则特别简单,那就是数值为 i 的数映射到下标为 i - 1 的位置。
using namespace std; class Solution { public: int firstMissingPositive(vector<int> &nums) { for (int i = 0; i < nums.size(); i++) { while (nums[i] != i + 1) { if (nums[i] <= 0 || nums[i] > nums.size() || nums[i] == nums[nums[i] - 1]) break; // 将nums[i] 放置到对应位置上[1,2,3...] int idx = nums[i] - 1; nums[i] = nums[idx]; nums[idx] = idx + 1; } } for (int i = 0; i < nums.size(); i++) { if (nums[i] != (i + 1)) { return (i + 1); } } return (nums.size() + 1); }