LeetCode 268 丢失的数字 -- JavaScript

716 阅读3分钟

题目

题目链接:leetcode-cn.com/problems/mi…

image.png

题解

在本题的解答中,充分的考虑了时间和空间复杂度,并且从位运算和数学计算的角度分析了此题的算法实现;

  1. 时间复杂度为 O(n^2) ,空间复杂度为 O(1) 的暴力枚举法;
  2. 时间复杂度为 O(nlog_2n),空间复杂度为 O(1) 的先排序再找不存在的数;
  3. 时间复杂度为 O(n),空间复杂度为 O(n) 的借助 数组 或 Set 的方法;
  4. 时间复杂度为 O(n),空间复杂度为 O(1) 的使用异或操作的方法;
  5. 时间复杂度为 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; 
};
​

有个问题,要考虑求和的溢出吗?实际上根据本题给的限制:

image-20211106181356550.png

n 是小于等于 10^{4} 的,所以对于求前 n 项和来说,和的规模小于 10^{8} ,而在 JavaScript 中,Number 使用 IEE754 的双精确度 64 位存储,其能表示的最大值的量级是 10^{304} ,所以不用考虑溢出问题;(JS中最大值存储在Number.MAX_VALUE中)



大家如果有更好的思路和解法,欢迎大家一起来讨论啊~