这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战
题目描述
一个数组,包含n个整数,整数的范围是在 [1,n] 之间,然后要找出在这个范围但是没有在数组里面的数,然后以数组的形式返回。
举个例子:
数组:[2,1,3,2,1]
返回:[4,5] n是5,然后4,5不在数组内,则返回
数组:[2,1,2]
返回:[3] n是3,然后3不在数组内,则返回
思路分析
第一种方法
这个题目可以从n这里入手,因为它是从1到n的数中找到数组不存在的数,则我们可以从1遍历到n,然后一个一个去数组中判断是否存在,如果存在则不处理,不存在则存到新数组中,最后返回新数组。
代码如下:
/**
* @param {number[]} nums
* @return {number[]}
*/
var findDisappearedNumbers = function (nums) {
const arr = []
for (let i = 1; i <= nums.length; i++) {
if (!nums.includes(i)) arr.push(i)
}
return arr
};
这个代码不长,看着简单,但是这个方法时间复杂度是O(n^2),因为你在for遍历里面又用了数组的includes方法来判断,includes也相当于一次遍历。
这样的结果就非常耗时,执行耗时要6秒多
第二种方法
下面来看另外一种方法
既然遍历嵌套遍历会导致复杂度变大,那么我们可以使用对象来做映射,这样遍历就不用嵌套了。首先先把1到n的数存到对象中,value都是true。
然后再次遍历数组,判断对象是否存在数组的值,如果存在,则把它删除。
最后对象剩下的就是数组没有数字,获取key,返回就可以了。
代码如下:
/**
* @param {number[]} nums
* @return {number[]}
*/
var findDisappearedNumbers = function (nums) {
var obj = {}
for (let i = 1; i <= nums.length; i++) {
obj[i] = true
}
for (let i = 1; i <= nums.length; i++) {
if (obj[nums[i - 1]]) delete obj[nums[i - 1]]
}
return Object.keys(obj).map(num => +num)
};
这个方法比上面的方法好一点,时间复杂度降了,执行用时也变少了
第三种方法
上面的两种方法我们是用了额外空间,是否可以在不使用额外空间的情况下同时一次遍历就可以?
答案是可以的。
因为这个数组的长度是n,然后里面的值不会大于n,所以可以利用对数组的值减1后用n取余当作索引(因为值是从1开始的,减1,代表索引可以从0开始)。然后对这个索引的值加n。没有出现的索引,则它对应的值不会大于n。
然后再遍历数组,如果发现当前值小于等于n就证明当前索引是不在数组中,则把它push到新数组中,最后返回新数组。
代码如下:
/**
* @param {number[]} nums
* @return {number[]}
*/
var findDisappearedNumbers = function (nums) {
const arr = []
const n = nums.length
for (let i = 0; i < n; i++) {
const x = (nums[i] - 1) % n
nums[x] += n
}
for (let i = 0; i < n; i++) {
if (nums[i] <= n) arr.push(i + 1)
}
return arr
};
确实妙~
这个方法比前面两个方法更快了,耗时更少,同时时间复杂度没有增加,就是不好理解,大家可以试试。