面试官考数组去重时,你还在当算法题做?7种解法+避坑指南,让你面试少走弯路

面试官考数组去重时,你还在当算法题做?7种解法+避坑指南,让你面试少走弯路 日常开发中数组去重是高频业务场景面试时更是手写代码的“常客”。很多同学要么只会一种解法要么代码健壮性差、边界case处理不到位既踩坑又丢分。这篇文章我会拆解7种数组去重的实战解法从基础双循环到高效的Set/Map每一种都附可运行代码还会讲清每种方法的优缺点、踩坑点最后总结最优实践。不管是面试手写代码还是业务开发落地看完就能用一、先明确核心需求我们要实现的unique函数核心要求实现数组去重返回无重复元素的新数组严格参数校验避免非数组输入导致报错保证代码可读性兼顾性能与稳定性先定一个基础的函数注释和参数校验模板这是大厂代码规范的标配也是让AI比如Copilot更好理解代码的关键/** * 数组去重函数 * param {Array} arr - 待去重的数组 * returns {Array} 去重后的新数组 * throws {Error} 当输入非数组时抛出错误 */functionunique(arr){// 第一步参数校验永远不要相信用户输入if(!Array.isArray(arr)){thrownewError(参数必须是数组类型);}// 去重逻辑后续补充}二、7种实战解法从基础到高效解法1双循环去重最基础面试保底这是最原始的解法核心是两层循环对比元素新手也能快速手写。functionunique(arr){if(!Array.isArray(arr)){thrownewError(参数必须是数组类型);}constres[];// 外层循环遍历原数组for(leti0;iarr.length;i){letisRepeatfalse;// 内层循环对比已存入res的元素for(letj0;jres.length;j){if(arr[i]res[j]){isRepeattrue;break;}}// 无重复则加入resif(!isRepeat){res.push(arr[i]);}}returnres;}// 测试用例console.log(unique([1,2,3,2,5]));// [1,2,3,5]✅ 优点逻辑简单无API依赖面试手写不易卡壳❌ 缺点时间复杂度O(n²)数据量大时性能差⚠️ 踩坑提醒仅适用于基本数据类型数字、字符串引用类型对象、数组无法对比去重解法2indexOf去重简化版双循环利用indexOf判断元素是否已在结果数组中减少一层手动循环代码更简洁。functionunique(arr){if(!Array.isArray(arr)){thrownewError(参数必须是数组类型);}constres[];for(leti0;iarr.length;i){// indexOf返回-1表示未找到即无重复if(res.indexOf(arr[i])-1){res.push(arr[i]);}}returnres;}// 测试用例console.log(unique([1,2,3,2,5]));// [1,2,3,5]✅ 优点代码比双循环简洁易理解❌ 缺点时间复杂度仍为O(n²)indexOf底层还是循环⚠️ 踩坑提醒indexOf对NaN识别有问题[NaN].indexOf(NaN) -1会导致NaN被重复保留解法3indexOf原数组下标对比优化版另一种indexOf用法判断当前元素的下标是否是第一次出现的下标是则保留。functionunique(arr){if(!Array.isArray(arr)){thrownewError(参数必须是数组类型);}returnarr.filter((item,index){// 只有当前下标等于第一次出现的下标才保留returnarr.indexOf(item)index;});}// 测试用例console.log(unique([1,2,3,2,5]));// [1,2,3,5]✅ 优点代码极简一行filter搞定❌ 缺点同样O(n²)复杂度且仍有NaN识别问题⚠️ 踩坑提醒filter会返回新数组无需手动创建res但性能无本质提升解法4先排序再去重性能提升但破坏原顺序先通过sort()排序再遍历对比前一个元素时间复杂度降到O(nlogn)排序的时间。functionunique(arr){if(!Array.isArray(arr)){thrownewError(参数必须是数组类型);}// 空数组直接返回if(arr.length0)return[];// 先排序注意sort默认是字符串排序数字需传比较函数constsortedArrarr.sort((a,b)a-b);constres[sortedArr[0]];for(leti1;isortedArr.length;i){// 对比当前元素和前一个元素if(sortedArr[i]!sortedArr[i-1]){res.push(sortedArr[i]);}}returnres;}// 测试用例console.log(unique([1,2,3,2,5]));// [1,2,3,5]✅ 优点性能比前三种好时间复杂度更低❌ 缺点破坏原数组的顺序稳定性丢失且sort对复杂类型排序易出错⚠️ 踩坑提醒数字数组排序必须传(a,b)a-b否则[10,2,5]会被排成[10,2,5]解法5对象字面量去重空间换时间O(n)复杂度利用对象的属性唯一性遍历数组时将元素作为对象key存在则跳过不存在则存入。functionunique(arr){if(!Array.isArray(arr)){thrownewError(参数必须是数组类型);}constobj{};constres[];for(leti0;iarr.length;i){constitemarr[i];// 避免类型转换导致的误判比如1和1constkeytypeofitemitem;if(!obj[key]){obj[key]true;res.push(item);}}returnres;}// 测试用例console.log(unique([1,2,2,3,2,5]));// [1,2,2,3,5]✅ 优点时间复杂度O(n)性能大幅提升❌ 缺点需要额外的对象存储空间复杂度O(n)⚠️ 踩坑提醒必须加typeof item否则数字1和字符串’1’会被判定为重复解法6Set去重ES6极简高效ES6新增的Set数据结构本身就是“无重复值的集合”一行代码就能搞定开发首选。functionunique(arr){if(!Array.isArray(arr)){thrownewError(参数必须是数组类型);}// Set转数组两种写法都可以// 写法1Array.from// return Array.from(new Set(arr));// 写法2扩展运算符return[...newSet(arr)];}// 测试用例console.log(unique([1,2,3,2,5,NaN,NaN]));// [1,2,3,5,NaN]✅ 优点代码极简O(n)复杂度支持NaN去重解决indexOf的痛点❌ 缺点ES6语法如需兼容低版本浏览器需转译⚠️ 踩坑提醒Set对引用类型如{}、[]无法去重因为引用地址不同解法7Map去重ES6灵活扩展Map和Set原理类似但可存储键值对适合需要额外处理元素的场景。functionunique(arr){if(!Array.isArray(arr)){thrownewError(参数必须是数组类型);}constmapnewMap();constres[];for(leti0;iarr.length;i){constitemarr[i];if(!map.has(item)){map.set(item,true);res.push(item);}}returnres;}// 测试用例console.log(unique([1,2,3,2,5,NaN,NaN]));// [1,2,3,5,NaN]✅ 优点O(n)复杂度支持NaN比对象字面量更安全避免key冲突❌ 缺点代码比Set稍繁琐同样依赖ES6⚠️ 踩坑提醒和Set一样引用类型无法通过地址去重三、踩坑汇总最优实践常见踩坑点忽略参数校验传入undefined/null/字符串时代码直接报错未处理NaNindexOf解法会保留多个NaNSet/Map则能正确去重引用类型去重所有基础解法都无法对{}/[]去重需额外深对比排序陷阱数字数组排序未传比较函数导致排序错误最优实践日常开发现代浏览器/Node.js优先用Set解法极简高效面试手写无ES6限制先写indexOf解法简洁再补充Set解法体现进阶兼容低版本浏览器用对象字面量解法兼顾性能和兼容性需保留原数组顺序避免用排序去重解法四、总结数组去重看似简单实则能考察对数组API、数据结构、时间/空间复杂度的理解。本文从参数校验的健壮性出发拆解了7种解法基础层双循环、indexOf面试保底性能层排序去重、对象字面量空间换时间进阶层Set、MapES6高效方案核心记住参数校验是前提性能和稳定性按需选日常开发优先Set面试要能讲清每种解法的优缺点。最后我把所有解法的完整代码、测试用例整理成了一个可直接运行的文件包含边界case如空数组、NaN、混合类型的测试需要的同学可以查看完整源码可私信/评论区获取。如果这篇文章对你有帮助欢迎点赞收藏后续还会分享更多手写代码、面试实战的干货