题目
题目链接:leetcode-cn.com/problems/mi…
题解
在本题的解答中,充分的考虑了时间和空间复杂度,并且从位运算和数学计算的角度分析了此题的算法实现;
- 时间复杂度为 O(n^2) ,空间复杂度为 O(1) 的暴力枚举法;
- 时间复杂度为 O(nlog_2n),空间复杂度为 O(1) 的先排序再找不存在的数;
- 时间复杂度为 O(n),空间复杂度为 O(n) 的借助 数组 或 Set 的方法;
- 时间复杂度为 O(n),空间复杂度为 O(1) 的使用异或操作的方法;
- 时间复杂度为 O(n),空间复杂度为 O(1) 的借助题目的数学概念(前 n 项和)的方法;
1、暴力枚举法
使用时间复杂度为 O(n^2) 的暴力枚举法;
这里使用了 api,根据 ECMA2016规范中的实现 Array.prototype.includes() 的复杂度为 O(n);
/**
* @param {number[]} nums
* @return {number}
*/
var missingNumber = function(nums) {
let len = nums.length,
n = len;
for(let i = 0;i <= len;i++) {
if(!nums.includes(i)) {
return i;
}
}
};
2、先排序再找
一看到题目,我就想到先进行排序,然后再找到那个不存在的数,这么做的时间复杂度为 O(nlogn);
如下,先使用希尔排序将数组变成有序,再一次遍历找出没有出现的哪个数;
/**
* @param {number[]} nums
* @return {number}
*/
var missingNumber = function(nums) {
const len = nums.length;
// 直接插入排序
function directInsetSort(nums,start,step) {
let len = nums.length;
let temp = null;
for(let i = start + step;i < len;i = i+step){
// 找到插入位置、移动、插入
temp = nums[i];
// 插入
let j;
for(j = i;j >= step;j = j-step) {
if(temp < nums[j - step]) {
nums[j] = nums[j - step];
}else {
nums[j] = temp;
break;
}
}
if(j < step) {
nums[j] = temp;
}
}
}
// 希尔排序
function shellOrder(nums) {
let len = nums.length;
let step = Math.floor(len / 2);
while(step > 0) {
for(let i = 0;i < step;i++) {
directInsetSort(nums,i,step); // 希尔排序
}
step = Math.floor(step / 2);
}
}
shellOrder(nums);
for(let i = 0;i < len;i++) {
if(nums[i] != i) {
return i;
}
}
return len;
};
3、借助数组或Set
但是上面的算法的时间复杂度为 O(nlogn),能不能找出时间复杂度更低的算法?
按照空间换时间的指导思想,这是可以的,只需要使用一个数组 temp,一次遍历将 nums 中的所有数按 temp[nums[i]] = nums[i] 的形式存储就行了;
3.1 借助数组
/**
* @param {number[]} nums
* @return {number}
*/
var missingNumber = function(nums) {
let len = nums.length;
let temp = [];
for(let i = 0;i <= len;i++) {
temp[nums[i]] = nums[i];
}
for(let i = 0;i <= len;i++) {
if(temp[i] !== i) {
return i;
}
}
};
3.2 借助Set
/**
* @param {number[]} nums
* @return {number}
*/
var missingNumber = function(nums) {
let len = nums.length;
let temp = false;
let numSet = new Set(nums);
for(let i = 0;i <= len;i++) {
temp = numSet.has(i);
if(!temp) {
return i;
}
}
};
4、使用位运算异或操作
这个空间消耗也太大了吧~,如果遇到大量数据,内存会被挤爆;
于是想有没有有其它办法降低空间复杂度,根据题目我们知道 nums 中是连续的数字,因此可以使用位运算异或操作来记录数字是否出现;
思路有点像这题:leetcode-cn.com/leetbook/re…
/**
* @param {number[]} nums
* @return {number}
*/
var missingNumber = function(nums) {
let len = nums.length;
let temp = 0;
for(let i = 0;i < len;i++) {
temp = temp ^ i ^ nums[i];
}
temp = temp ^ len;
return temp;
};
5、数学方法
最后,可以根据数学计算的角度去求解这个问题;
前 n 项和减去数组的所有数之和就是那个没有出现在数组中的那个数;
/**
* @param {number[]} nums
* @return {number}
*/
var missingNumber = function(nums) {
const len = nums.length;
let nSum = 0,
arraySum = 0;
// 求前 n 项和, n = len
nSum = ( len + 1 ) * len / 2 ;
// 计算数组中所有数的和
arraySum = nums.reduce((total,currentvalue) => {
return total + currentvalue;
},0);
return nSum - arraySum;
};
有个问题,要考虑求和的溢出吗?实际上根据本题给的限制:
n 是小于等于 10^{4} 的,所以对于求前 n 项和来说,和的规模小于 10^{8} ,而在 JavaScript 中,Number 使用 IEE754 的双精确度 64 位存储,其能表示的最大值的量级是 10^{304} ,所以不用考虑溢出问题;(JS中最大值存储在Number.MAX_VALUE中)
大家如果有更好的思路和解法,欢迎大家一起来讨论啊~