JavaScript 数组去重方法总结

2,406 阅读4分钟

数组去重是常见的面试题目,相信每个面试者总能答出一两种方法。

网上看了不少实现方法,我觉得从性能开销方面,来理解这些方法,效果更好。

下面,我根据时间复杂度,总结了4种方法。

时间复杂度 O(n2)

  1. 双重循环
  2. indexOf 方法

时间复杂度 O(n)

  1. Object 作为哈希表
  2. ES6 Set 数据结构

时间复杂度 O(n2)

1. 双重循环

双重 for 循环时最容易想到的方法,每次从原数组取出一个元素,拿它和新数组一一比较。

若新数组没有与该元素相等的元素,就将其放入新数组中;否则不放入。

最后,新数组就都是不重复的元素。

这种方法需要用到两次 for 循环,时间复杂度是 O(n2)。

var unique = function(arr) {
  var newArr = [];
  for (var i = 0; i < arr.length; i++) {
    for (var j = 0; j < newArr.length; j++) {
      if (newArr[j] === arr[i]) break;
    }
    if (j === newArr.length) newArr.push(arr[i]);
  }
  return newArr;
};

2. indexOf() 方法

上面的方法中,第二个 for 循环的目的,是判断新数组是否存在相同的元素。

因此,我们可以利用数组方法indexOf()来实现,这样写起来的代码更加简洁。

当新数组不存在该元素,indexOf()方法会返回-1

虽然代码看起来只用到了一次循环,但indexOf()方法实现原理也是循环,所以时间复杂度还是 O(n2)。

需要注意,indexOf()是 ES5 新增的方法,IE8 及以前的浏览器不支持

var unique = function(arr) {
  var newArr = [];
  for (var i = 0; i < arr.length; i++) {
    if (newArr.indexOf(arr[i]) === -1) newArr.push(arr[i]);
  }
  return newArr;
};

现在,我们还可以让代码更简洁。

不妨使用数组filter()方法。同样 IE8 及以前的浏览器不支持

var unique = function(arr) {
  var newArr = arr.filter((item, index, array) => {
    // 这一步很有趣,indexOf 方法返回第一个重复元素的索引。
    // 当遍历到某个重复元素的第二个索引,由于和 indexOf 返回的不一样。
    // 因此下面的语句会返回 false,当前 item 不会添加到 newArr,最终实现去重。
    return array.indexOf(item) === index;
  });
  return newArr;
};

时间复杂度 O(n)

3. Object 作为散列表

上面的方法,每次为了判断元素是否在新数组中,都要对行遍历,这个开销比较大。

有没有一种方式直接访问该元素是否存在,而不必从头到尾遍历一边。

学过数据结构,估计想起散列表就有这样的优点:读取操作很高效。

因此,可以将对象作为散列表,实现原理是:

遍历数组,用对象来记录当前元素是否已经在新数组中;若不存在,则添加到新数组,否则跳过。

需要注意,JS 中,对象的 key 值都是字符串。

obj[1]obj["1"]是等价的,数字1会转换成字符串"1",无法分辨出他们区别。

因此代码中,需要为 key 值添加类型信息:key = arr[i] + typeof arr[i]

var unique = function(arr) {
  var hash = {};
  var newArr = [];
  for (let i = 0; i < arr.length; i++) {
    var key = arr[i] + typeof arr[i]; 
    if (hash[key] !== true) {
      newArr.push(arr[i]);
      hash[key] = true;
    }
  }
  return newArr;
};

上面的代码,你还可以简化一下:

var unique = function(arr) {
  var hash = {};
  return arr.filter(item => {
    var key = item + typeof item;
    return !hash[key] ? (hash[key] = true) : false;
  });
};

4. ES6 Set 数据结构

此外,还可以利用 ES6 提供的 Set 数据结构。

如果运行环境支持 ES6,推荐使用这个去重方案。

数据结构 Set 类似于数组,但是它的每个元素都是唯一的。

Set函数可以接受一个数组作为参数,用来初始化。

最终代如下:

var unique = function(arr) {
  return [...new Set(arr)];
};

总结

以上方法,只适用于包含 String 或 Number 类型元素的数组,若数组中含有 Object 元素,会使结果出现难以预料的情况。

另外,针对纯数字的数组,进行性能测试。

当数据量 1000 以内时,性能的差异可以忽略不计。

当数据量在一百万量级时,表现如下:

由图中,可以得出性能对比:

ES6 Set > Object 作为哈希表 > 双重 for 循环 > indexOf() 方法