这里提供三种思路。
第一种解法,如果不管时间复杂度和内存,可以从1开始依次枚举正整数一直到数组中的最大值,并遍历数组,判断其是否在数组中。这个解法时间复杂度是O(N),有点高。
int firstMissingPositive(int* nums, int numsSize){
int MaxN = 0;
int i, j;
for (int i = 0; i < numsSize; ++i)
if (nums[i] > MaxN)
MaxN = nums[i];
for (i = 1; i <= MaxN; ++i){
for (j = 0; j < numsSize; ++j)
if (nums[j] == i) break;
if (j == numsSize)
return i;
}
return MaxN + 1;
}
第二种解法,既然这个数组未排序,那么就排序,再对数组进行处理。首先使用快速排序法从小到大进行排序,然后开始遍历数组中的每个数,如果第一个元素就大于或等于2,那么直接返回1,否则继续遍历,直到找到大于或等于2的整数,这时判断该值是否与前一值连续或相等(差值小于或等于1),如果连续或相等,则继续遍历,否则直接返回前一值+1。如果数组遍历完,函数仍未返回,有两种情况:第一种情况,该数组全部小于或等于1,则根据最后一个数判断返回1还是2,如果最后一个数小于等于0,则返回1,如果最后一个数等于1,则返回2;第二种情况,该数组有小于或等于1的部分也有大于1的部分,且大于1的部分与前一值一直保持连续或相等的状态,此时应返回数组的最后值+1。快速排序的时间复杂度是O(NlogN),遍历数组的时间复杂度是O(N),总体来说时间复杂度是O(NlogN)。
void quick_sort(int s[], int l, int r)
{
if (l < r)
{
//Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1
int i = l, j = r, x = s[l];
while (i < j)
{
while (i < j && s[j] >= x) // 从右向左找第一个小于x的数
j--;
if (i < j)
s[i++] = s[j];
while (i < j && s[i] < x) // 从左向右找第一个大于等于x的数
i++;
if (i < j)
s[j--] = s[i];
}
s[i] = x;
quick_sort(s, l, i - 1); // 递归调用
quick_sort(s, i + 1, r);
}
}
int firstMissingPositive(int* nums, int numsSize){
int i, res;
if (numsSize == 0)
res = 1;
else if (numsSize == 1){
if (nums[0] <= 0) res = 1;
else if (nums[0] == 1) res = 2;
else res = 1;
}
else{
quick_sort(nums, 0, numsSize-1);
if (nums[0] <= 1){
for (i = 1; i < numsSize; ++i){
if (nums[i] >= 2)
if (nums[i - 1] <= 0){
res = 1;
break;
}
else if (nums[i] - nums[i - 1] >1) {
res = nums[i - 1] + 1;
break;
}
}
if (i == numsSize){
if (nums[i - 1] <= 0) res = 1;
else if (nums[i - 1] == 1) res = 2;
else res = nums[i - 1] + 1;
}
}
else res = 1;
}
return res;
}
第三种解法,官方解法的第一种解法,很给力。这里我就不把官方的原话搬过来了,感兴趣可以看官方教程https://leetcode-cn.com/problems/first-missing-positive/solution/que-shi-de-di-yi-ge-zheng-shu-by-leetcode-solution/。总结一下就是以下三步:
第一步,遍历数组,把每个小于等于0的数变成N+1,那么整个数组就变成了全为正整数的数组。
第二步,遍历新的数组,如果第i个元素的绝对值小于或等于N(这里取绝对值是因为该元素可能被改过成负数),则把对应的那个元素改成负数(如果是正数则取相反数,如果是负数那么不变),比如a[2]=4,C语言数组中第4个元素是a[3],此时如果a[3]=6,则把a[3]改成-6,如果a[3]=-6(证明之前修改过一次),则保持负数不变,当遍历到a[3]时,再把a[5]改成负数(abs(a[3])=6,对应a[5])。
前两步的目的相当于对原数组中出现在[1,N]区间的数x打上了“标记”,标记在第a[x-1]元素上,有“标记”的位置上的元素全部都是负数。未出现的正数,可以通过新数组中未被“标记”过的元素的位置来确定,如果新数组中某元素大于0,则说明未做“标记”,即原数组中没有出现过该正数,自然就不会在对应位置有“标记”。所以第三步,遍历第二步修改好的数组,找到第一个大于0的元素,该元素所在的位置索引+1即为原数组中缺失的第一个正数。
int firstMissingPositive(int* nums, int numsSize) {
for (int i = 0; i < numsSize; ++i) {
if (nums[i] <= 0) {
nums[i] = numsSize + 1;
}
}
for (int i = 0; i < numsSize; ++i) {
int num = abs(nums[i]);
if (num <= numsSize) {
nums[num - 1] = -abs(nums[num - 1]);
}
}
for (int i = 0; i < numsSize; ++i) {
if (nums[i] > 0) {
return i + 1;
}
}
return numsSize + 1;
}
官方的解法很强,时间复杂度是O(n),但不清楚为什么内存消耗稍微比第二种方法多一点点。。