【困难】算法:缺失的第一个正数

108 阅读1分钟

缺失的第一个正数

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

41. 缺失的第一个正数

算题

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

示例 1:

输入: nums = [1,2,0]
输出: 3

示例 2:

输入: nums = [3,4,-1,1]
输出: 2

示例 3:

输入: nums = [7,8,9,11,12]
输出: 1

提示:

  • 1 <= nums.length <= 5 * 105
  • -231 <= nums[i] <= 231 - 1

解析

第一种思维是采用暴力破解的方法,在数量级不算很大的情况下,暴力破解法无疑是一种既简单又通用的解题方法。即将给定数组遍历,一次判断从 0 - 最大值 是否都出现。

暴力破解法

依题可知数字最大的为 2 的 31 次方,那么我们就可以从1开始遍历,直至 2的 31 次方,依次去判断在给定的数组中是否存在。

/**
 * @param {number[]} nums
 * @return {number}
 */
 var firstMissingPositive = function(nums) {
  const max = Math.pow(2, 31) - 1;
  for (let index = 1 ; index < max ; index++) {
      if (nums.indexOf(index) === -1) {
          return index
      }
  }
};

在诸多的测试用例中,这种方法仅仅可以通过前面几个测试用例。当数量级很大,并且缺失的正整数位置靠后的时候,就要求极大的计算量。每一次的 indexOf 是一个非常大的计算和消耗,以此提交的结果为超出时间限制,很明显,这种方法是不可取的。

排序法

那么不难想到,假设不用 indexOf 是不是就可以优化上面的算法了。不用 indexOf要找出缺失的正整数就要求数组是有序排列的,所以我们可以尝试去使用排序法,然后再一次判定。

const quickSort = function (arr) {
  if (arr.length === 0) {
    return []
  }
  // 去第一个值作为标数
  let standard = arr[0]
  // 遍历数组,小于标数放左边, 大于标数放右边
  const left = []
  const right = []
  for (let index = 1; index < arr.length; index++) {
    const number = arr[index]
    if (number >= 1) {
        if (number < standard) {
         left.push(number)
        } else {
            right.push(number)
        }
    }
   
  }
  // 递归调用
  return [...quickSort(left), standard, ...quickSort(right)]
}

var firstMissingPositive = function(nums) {
    nums = quickSort(nums)
    console.log(nums)
    let index = 1;
    let numberIndex = 0
    while(numberIndex < nums.length) {
        if (index < nums[numberIndex]) {
            return index
        } else if (index === nums[numberIndex]) {
            index++
            numberIndex++
        } else {
            numberIndex++
        }
    }
    return index
};

这里采用了快速排序的算法,但结果提交依然显示错误。原因是数量级过大,排序之后还有一次 while的循环,很显然是一种比较消耗性能的算法了。

计数法

综上可以总结出,当数据量很大的时候,想要去排序或者作用于同一个算法寻找缺失数是很难做到的。因为每一次都会对一个非常大的数据量进行计算。会消耗很长的时间,导致超出时间限制。这里可以使用空间代替时间的方法去解决。

在排序算法中,有一个非常经典的排序算法,叫计数排序又叫 桶排序,原理是,将数组中对应的数值作为计数数组的下标,那么对应下标的值表示该数出现的次数。

var firstMissingPositive = function (nums) {
  const dpList = new Array(nums.length + 1).fill(0) // 创建一个长度和目标数量相等的数组,并且每一个元素为0,表示每一个数字出现的次数为0
  dpList[0] = 1 // 由于是找正整数,所以先把0默认已经出现,即不考虑0
  for (let index = 0; index < nums.length; index++) { // 一次遍历数组
    if (nums[index] > 0) { // 排除负数
      dpList[nums[index]] = dpList[nums[index]] + 1 // 数字所对应下标的数量+1,表示该下标出现过一次,即该数出现过一次
    }
  }
  const zeroIndex = dpList.findIndex(item => item === 0); // 找到下标为0的元素,即没有出现的数

  return zeroIndex === -1 ? dpList.length : zeroIndex // 如果数组中都出现了,那么数组中没有缺失的正整数,那么可以理解为缺失的是数组之外的第一个数(例如: 1,2,3 -> 那么数组缺失的4)

};

附上提交结果:

image.png