数组去重是常见的面试题目,相信每个面试者总能答出一两种方法。
网上看了不少实现方法,我觉得从性能开销方面,来理解这些方法,效果更好。
下面,我根据时间复杂度,总结了4种方法。
时间复杂度 O(n2)
- 双重循环
- indexOf 方法
时间复杂度 O(n)
- Object 作为哈希表
- 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() 方法