前言
数组去重是所有语言的算法题中绕不开的一个问题,也是在很多大厂面试的时候会碰到的常考题。这是每个小白必须要打败的“怪”。那么,今天,就跟着我的脚步,我们一起来浅谈一下js中的数组去重方法。
方法一
双层for循环:
双层for循环是一个所谓的“笨方法”,但是也是最基础的方法,其根本逻辑是:
-
定义了一个空数组
newArr,用来存放去重后的元素。 -
遍历原数组
arr的每一个元素arr[i]:-
对于每个元素,检查它是否已经存在于新数组
newArr中:- 如果在新数组中找到与当前元素相等的值,则跳出内层循环(说明该值已存在)。
-
如果内层循环完成后,
j === newArr.length(即在新数组中未找到重复的值),则将当前元素添加到newArr中。
-
-
最后返回
newArr,即去重后的数组。
const arr = [1, 2, 3, 4, 2, 1]
function unique(arr) {
let newArr = []
for (let i = 0; i < arr.length; i++) {
//新数组是否已经具有该值
for (var j = 0; j < newArr.length; j++) {
if (arr[i] === newArr[j]) {
break
}
}
if (j === newArr.length) {//j长度等于新数组的长度时,则证明没有
newArr.push(arr[i]) //若没有则添加到新数组
//arr[i]
}
}
return newArr
}
console.log(unique(arr));
}
这个方法虽然基础,但是用了双层的循环,所以它的时间复杂度是O(n^2),如果数组长度很大,效率会很低。
方法二
for+indexOf
当我们用方法一敲出数组去重的代码后,我们会思考一个问题:有没有一个方法可以代替双层for循环方法的第二层循环呢?也就是有没有方法可以判断一个数组是否存在一个指定个元素呢?当然有!数组的indexOf()方法
数组的indexOf()方法:可返回某个指定的元素在数组中首次出现的位置(下标),若不存在这个元素就返回-1。
结合for循环,for+indexOf 的数组去重方法具体逻辑如下:
-
定义一个空数组
newArr,用于存放去重后的元素。 -
遍历原数组
arr的每个元素:-
使用
indexOf方法检查当前元素arr[i]是否已经存在于newArr中。-
如果
indexOf返回-1,说明该元素不在newArr中:- 将该元素添加到
newArr中。
- 将该元素添加到
-
-
-
返回去重后的新数组
newArr。
const arr = [1, 2, 3, 4, 2, 1]
function unique(arr) {
let newArr = []
for (let i = 0; i < arr.length; i++) {
if( newArr.indexOf(arr[i])=== -1)){
newArr.push(arr[i])
}
}
return newArr
}
console.log(unique(arr));
}
方法三
for+includes
方法三的底层逻辑和方法二的基本一致,只是判断一个数组是否存在一个指定个元素的方法变为了 数组的includes方法
includes()方法:用于判断数组中是否包含某个特定的元素。它返回一个布尔值:如果数组中包含该元素,返回true;否则返回false
加上for后的核心逻辑:
-
定义一个空数组
newArr,用于存放不重复的元素。 -
遍历原数组
arr的每个元素:-
使用
includes方法判断newArr中是否已包含当前元素:- 如果
newArr中不存在该元素,则将其添加到newArr中。
- 如果
-
-
最后返回去重后的数组
newArr。
const arr = [1, 2, 3, 4, 2, 1]
function unique(arr) {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
if (!newArr.includes(arr[i])) { // 当 newArr 不包含 arr[i] 时,加入 newArr
newArr.push(arr[i]);
}
}
return newArr;
}
console.log(unique(arr)); // 输出:[1, 2, 3, 4]
方法四
filter + sort 方法
在谈这个方法之前,我们需要了解什么是 filter()方法 和 sort()方法
filter()方法: 用于创建一个新的数组,新数组中的元素是原数组中满足指定条件的元素。即它用于对数组进行筛选,返回所有符合条件的元素,原数组不改变。
sort() 方法:用于对数组的元素进行排序,默认按照字符编码的顺序进行升序排列。
当我们了解了 filter()方法 和 sort()方法 之后,再思考代码逻辑就很简单了,当我们把数组用 sort()方法 排好序后,只需要不断地将当前数组中的元素和上一个比较,当前元素只有不等于前一个元素时,才保留当前元素,具体代码实现如下:
-
初始化新数组
newArr:let newArr = [...arr];- 使用扩展运算符
...创建arr数组的一个浅拷贝,确保对newArr的修改不会影响原始数组arr。
- 使用扩展运算符
-
排序数组:
调用
sort()方法对newArr数组进行排序。newArr.sort((a, b) => a - b); -
去重操作:
return newArr.filter((item, index, array) => { return index === 0 || item !== array[index - 1]; });-
使用
filter()方法对数组进行去重:-
filter()的回调函数接受三个参数:当前元素item,当前元素的索引index,以及原始数组array。 -
在回调函数中:
index === 0:对第一个元素来说,直接返回true,保留该元素。item !== array[index - 1]:对于后续的元素,只有当当前元素不等于前一个元素时,才返回true,即保留当前元素,达到去重的效果。
-
-
-
返回去重后的新数组:
- 最终返回
newArr,它是去重且排序后的数组。
- 最终返回
完整代码:
const arr = [1, 2, 3, 4, 2, 1]
function unique(arr) {
let newArr = [...arr]; // 创建新数组
newArr.sort((a, b) => a - b); // 按数字大小排序
return newArr.filter((item, index, array) => {
return index === 0 || item !== array[index - 1]; // 去重
});
}
console.log(unique(arr); // 输出:[1, 2, 3, 4]
方法五
filter + {} 方法
在这个方法里,我们首先要知道一个关于对象的小知识,那就是,对象里的key值不能重复,只能唯一!既然如此...那么一切就好办了!具体代码逻辑如下:
-
初始化一个空对象
obj:let obj = {};- 用于记录数组中已经出现的元素,键是数组的值,值是布尔类型
true,表示该值已存在。
- 用于记录数组中已经出现的元素,键是数组的值,值是布尔类型
-
使用
filter()方法过滤重复值:return arr.filter((item, index, array) => { return obj[item] ? false : (obj[item] = true); });-
遍历数组
arr:-
filter()的回调函数返回布尔值,决定是否保留当前元素。 -
检查当前元素
item是否已存在于obj中:- 如果存在(
obj[item]为true),返回false,不保留该元素。 - 如果不存在,将其添加到
obj中,并设置为true,同时返回true,保留该元素。
- 如果存在(
-
-
完整代码如下
let arr = [1,2,3,1,2]
function unique(arr){
let obj ={}
return arr.filter((item,index,array)=>{
return obj[item] ? false: (obj[item]=true)
})
}
console.log(unique(arr))//输出结果[1,2,3]
方法六
Set 方法
Set 是一种ES6 提供了新的数据结构,用于存储唯一值。也就是说Set 中的所有值都是唯一的,不允许重复。如果向 Set 添加重复的值,则只会保留一个。
假设输入数组为 [1, 2, 3, 2, 1]:
-
创建一个
Set:new Set(arr) // 结果为 Set {1, 2, 3}Set自动去掉了数组中的重复元素。
-
将
Set转换为数组:[...new Set(arr)] // 结果为 [1, 2, 3] -
最终返回去重后的数组
[1, 2, 3]。
完整代码如下:
let arr = [1, 2, 3, 2, 1]
function unique(arr){
return [...new Set(arr)]
}
console.log(unique(arr))
时间复杂度
以上六种办法各有千秋,但东西总分好坏,六种方法的时间复杂度也各有不同。
| 方法 | 时间复杂度 | |
|---|---|---|
双层 for 循环 | O(n²) | 遍历数组一次,复杂度为 O(n),双层循环复杂度为 O(n²) |
for + indexOf | O(n²) | indexOf 方法本质上是一次线性查找,最坏复杂度为 O(n) |
for + includes | O(n²) | includes 的底层实现与 indexOf 类似,也是 O(n) |
filter + sort | O(n log n) | sort 通常为 O(n log n) ,filter 遍历一次数组,复杂度为 O(n) 整体复杂度为 O(n log n) |
filter + 对象 | O(n) | 遍历数组一次,复杂度为 O(n)。对象属性的存取操作为 O(1) |
Set | O(n) | Set 的添加和检查操作为 O(1),遍历数组一次为 O(n) |
当我们碰上不同的使用场景,可以选择对应的数组去重的“最优解”。
总结
在这篇文章里,我们一起浅谈了一下js中数组去重的六种办法,看到这儿,恭喜你,你已经学会了js中的数组去重,打败了数组去这只小怪物,接下来的日子里,我们一起努力学习,继续在学习js的路上打怪升级吧!