缺失的第一个正数
一起养成写作习惯!这是我参与「掘金日新计划 · 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)
};
附上提交结果: