23、数组去重有哪些方式?

张开发
2026/4/14 7:16:14 15 分钟阅读

分享文章

23、数组去重有哪些方式?
目录一、常见数组去重方式1. Set 去重最常用推荐优点原理时间复杂度空间复杂度2. for indexOf原理时间复杂度缺点3. for includes优点时间复杂度4. filter indexOf原理时间复杂度缺点5. reduce 去重优点缺点6. 利用对象 Object 作为哈希表优点缺点时间复杂度7. Map 去重优点时间复杂度8. 排序后去重原理时间复杂度缺点二、不同方式对比三、对象数组怎么去重方式1Map方式2reduce方式3保留最后一项说明四、Set 去重的底层理解面试加分点五、面试时怎么回答更精彩标准高分回答模板六、面试官最喜欢追问的点1. Set 能给对象数组去重吗2. indexOf 和 includes 的区别3. Object 和 Map 做去重有什么区别ObjectMap4. 去重后需要保持原顺序吗七、如果面试官让你手写推荐这样写1. 最简版2. 基础版不用 Set3. 高性能版Map4. 对象数组按字段去重八、一句话总结面试收尾很好用数组去重是前端/JavaScript 面试中的高频题面试官通常不只是想听你说出几种写法还会关注你是否知道多种实现方式是否清楚时间复杂度 / 空间复杂度是否知道不同数据类型下的差异是否能根据场景选最优方案是否能扩展到对象数组去重、NaN、null、引用类型等情况一、常见数组去重方式假设有数组const arr [1, 2, 2, 3, 4, 4, 5]1.Set去重最常用推荐const result [...new Set(arr)] console.log(result) // [1, 2, 3, 4, 5]优点写法最简洁性能好面试中最推荐先说这个原理Set中的值是唯一的重复值会被自动过滤。时间复杂度O(n)空间复杂度O(n)2.for indexOfconst result [] for (let i 0; i arr.length; i) { if (result.indexOf(arr[i]) -1) { result.push(arr[i]) } } console.log(result)原理遍历原数组如果结果数组中不存在当前值再放进去。时间复杂度indexOf本身是 O(n)总体O(n²)缺点性能一般无法正确处理NaN例如console.log([NaN].indexOf(NaN)) // -13.for includesconst result [] for (const item of arr) { if (!result.includes(item)) { result.push(item) } } console.log(result)优点比indexOf语义更清晰可以识别NaN时间复杂度O(n²)4.filter indexOfconst result arr.filter((item, index) { return arr.indexOf(item) index }) console.log(result)原理只保留每个元素第一次出现的位置。时间复杂度O(n²)缺点对NaN不友好const arr2 [1, NaN, NaN] const result2 arr2.filter((item, index) arr2.indexOf(item) index) console.log(result2) // [1]因为indexOf(NaN)永远是-15.reduce去重const result arr.reduce((prev, cur) { if (!prev.includes(cur)) { prev.push(cur) } return prev }, []) console.log(result)优点可以展示你对函数式编程的理解缺点本质上还是includes时间复杂度一般是O(n²)6. 利用对象Object作为哈希表const arr [1, 2, 2, 3, 4, 4, 5] const obj {} const result [] for (const item of arr) { if (!obj[item]) { obj[item] true result.push(item) } } console.log(result)优点思路经典在早期没有Set时很常见缺点对 key 会做字符串转换1和1可能混淆对对象、复杂类型不友好例如const arr [1, 1]对象 key 最终都会变成字符串处理时容易出问题。时间复杂度O(n)7.Map去重const map new Map() const result [] for (const item of arr) { if (!map.has(item)) { map.set(item, true) result.push(item) } } console.log(result)优点比Object更安全支持任意类型作为 key面试中说出来会显得比较全面时间复杂度O(n)8. 排序后去重const arr [3, 1, 2, 2, 4, 4, 5] const sorted arr.slice().sort((a, b) a - b) const result [] for (let i 0; i sorted.length; i) { if (i 0 || sorted[i] ! sorted[i - 1]) { result.push(sorted[i]) } } console.log(result) // [1, 2, 3, 4, 5]原理先排序让相同元素相邻再做一次线性扫描。时间复杂度排序O(n log n)遍历O(n)总体O(n log n)缺点会改变原有顺序如果不拷贝原数组对对象数组不直接适用二、不同方式对比方式是否推荐时间复杂度是否支持NaN特点Set非常推荐O(n)支持最简洁现代开发首选for indexOf一般O(n²)不支持基础写法for includes一般O(n²)支持可读性较好filter indexOf一般O(n²)不支持常见但有坑reduce includes一般O(n²)支持展示函数式写法Object哈希了解即可O(n)一般有类型转换问题Map推荐O(n)支持比Object更稳妥排序后去重特定场景O(n log n)视实现而定适合有序处理三、对象数组怎么去重面试常会追问普通数组会了那对象数组呢例如const list [ { id: 1, name: A }, { id: 2, name: B }, { id: 1, name: A2 } ]如果按id去重方式1Mapconst map new Map() const result [] for (const item of list) { if (!map.has(item.id)) { map.set(item.id, true) result.push(item) } } console.log(result)方式2reduceconst result list.reduce((prev, cur) { if (!prev.find(item item.id cur.id)) { prev.push(cur) } return prev }, []) console.log(result)方式3保留最后一项const result Array.from( new Map(list.map(item [item.id, item])).values() ) console.log(result)说明如果 key 重复后面的会覆盖前面的所以最终保留的是最后一个四、Set去重的底层理解面试加分点如果面试官问为什么Set能去重你可以这样答Set是 ES6 提供的数据结构成员值唯一。数组转成Set时重复值会被自动忽略再通过扩展运算符或Array.from转回数组即可。它的查找和插入平均时间复杂度接近 O(1)所以整体去重效率通常是 O(n)。另外Set判断是否相同采用的是类似SameValueZero的规则所以NaN也能正确去重。例如console.log([...new Set([1, 1, NaN, NaN])]) // [1, NaN]五、面试时怎么回答更精彩如果你想答得像“有层次、有深度”建议按下面结构说标准高分回答模板数组去重常见有几种方式。最推荐的是Set写法最简洁时间复杂度是O(n)const unique arr [...new Set(arr)]如果考虑兼容性或者考察基础实现也可以用for循环配合indexOf/includes但这类方法通常是O(n²)。另外还可以用Map或对象哈希表来记录是否出现过这类方式本质也是空间换时间复杂度可以做到O(n)。如果是对象数组去重通常不能直接用Set按内容去重因为对象比较的是引用地址这时一般会按某个字段比如id结合Map来实现。实际开发里我一般优先使用Set做基础类型数组去重如果是对象数组会用Map按业务字段去重同时明确是保留第一项还是最后一项。六、面试官最喜欢追问的点1.Set能给对象数组去重吗可以去掉同一个引用的重复对象但不能去掉内容相同、引用不同的对象。const a { id: 1 } const b { id: 1 } console.log([...new Set([a, b])]) // 两个都还在因为a ! b它们引用地址不同。2.indexOf和includes的区别indexOf返回索引不存在返回-1includes返回布尔值includes可以识别NaNindexOf不能识别NaN3.Object和Map做去重有什么区别Objectkey 只能是字符串或Symbol会发生隐式字符串转换容易碰到原型链问题Mapkey 可以是任意类型API 更语义化set / get / has更适合做哈希映射4. 去重后需要保持原顺序吗Set保持插入顺序Map也保持插入顺序排序去重通常会改变原顺序这是很容易被忽略的加分点。七、如果面试官让你手写推荐这样写1. 最简版function unique(arr) { return [...new Set(arr)] }2. 基础版不用 Setfunction unique(arr) { const result [] for (const item of arr) { if (!result.includes(item)) { result.push(item) } } return result }3. 高性能版Mapfunction unique(arr) { const map new Map() const result [] for (const item of arr) { if (!map.has(item)) { map.set(item, true) result.push(item) } } return result }4. 对象数组按字段去重function uniqueBy(arr, key) { const map new Map() return arr.filter(item { if (map.has(item[key])) { return false } map.set(item[key], true) return true }) } const list [ { id: 1, name: a }, { id: 2, name: b }, { id: 1, name: c } ] console.log(uniqueBy(list, id))八、一句话总结面试收尾很好用你可以最后这样总结数组去重我一般会分成三类来看现代写法Set最简洁适合基础类型数组传统写法indexOf、includes、filter适合考察基础原理工程写法Map/ 自定义uniqueBy适合对象数组和业务场景。实际开发中我会优先考虑数据类型、是否保序、是否按字段去重以及时间复杂度。

更多文章