数组 “找同款” 指南:从暴力循环到 Map 进阶

42 阅读5分钟

数组找重复值:5 种方法玩转数据匹配

你有没有过这样的经历?在代码里,想要实现 “两个数组找重复值” ,这是个简单的经典问题。今天解锁一下更多 “找重复” 的姿势~~~

一、暴力循环:最 “实在” 的笨办法

如果你刚入门,还没接触过复杂数据结构, “双重循环” 绝对是你的第一选择。就像在两个班级里逐个点名比对,只要名字一样,就记下来。

function findDuplicatesByLoop(arr1, arr2) {

 const duplicates = [];

 for (const item1 of arr1) {

   for (const item2 of arr2) {

     if (item1 === item2) {

       duplicates.push(item1);

       break; // 避免同一元素重复添加

     }

   }

 }

 return duplicates;

}

这种方法的优点就像白开水 —— 简单直观,不用记任何复杂 API。但缺点也很明显:如果两个数组各有 1000 个元素,就要循环 100 万次,数据量大时能卡到你怀疑人生。所以它只适合处理 “迷你数组”,比如找出两个小组里的共同成员。

二、filter+includes:一行代码搞定的 “偷懒神器”

如果你追求代码简洁,那 “filter+includes” 组合绝对能满足你。就像用放大镜在第二个数组里快速扫描,只要能找到和第一个数组匹配的元素,就留下来。

function findDuplicatesByFilter(arr1, arr2) {

 return arr2.filter(item => arr1.includes(item));

}

一行代码就能实现功能,是不是很酷炫?但别被表象迷惑 ——includes 方法本质也是循环查找,所以它的性能和双重循环差不多。不过对于日常开发中的小型数据,比如筛选两个商品列表里的共同品类,这种 “偷懒” 写法完全够用,还能让同事夸你代码写得优雅。

三、Set:性能与简洁的 “黄金平衡点”

如果说有哪种方法既好用又高效,那非 Set 莫属。把第一个数组转成 Set,就像给元素办了张 “身份证”,之后查找时只需 “刷证” 就能瞬间匹配,再也不用逐个比对。

function findDuplicatesBySet(arr1, arr2) {

 const set = new Set(arr1);

 return arr2.filter(item => set.has(item));

}

这种方法的时间复杂度是 O (n+m),比如处理 10 万条数据,比双重循环快 100 倍以上。而且代码量少,容易理解,堪称 “性价比之王”。唯一的小缺点是无法保留重复次数,比如两个数组都有两个 “2”,它会返回两个 “2”,如果想去重,只需再用 Set 包装一次结果即可。

四、排序双指针:不占空间的 “环保方案”

如果你的项目对内存要求极高,不能用额外的数据结构,那 “排序双指针” 就是你的救星。先给两个数组排序,就像把学生按身高排队,然后用两个指针分别遍历,遇到相同元素就记录下来,不同就移动较小的指针。

function findDuplicatesByPointer(arr1, arr2) {

 arr1.sort((a, b) => a - b);

 arr2.sort((a, b) => a - b);

 let i = 0, j = 0;

 const duplicates = [];

 while (i < arr1.length && j < arr2.length) {

   if (arr1[i] === arr2[j]) {

     duplicates.push(arr1[i]);

     i++;

     j++;

   } else if (arr1[i] < arr2[j]) {

     i++;

   } else {

     j++;

   }

 }

 return duplicates;

}

这种方法不用额外占用空间,但排序会改变原数组的顺序,如果你需要保留数组原本的排列,就得先拷贝一份再排序。不过对于处理大型数据且内存紧张的场景,比如服务器日志分析,它依然是不错的选择。

五、Map 进阶版:能统计次数的 “全能选手”

如果需求更复杂,比如要找出两个数组中重复元素的 “最小出现次数”,那 Map 进阶版就能派上用场。用 Map 记录第一个数组中元素的出现次数,然后遍历第二个数组,每找到一个匹配元素,就把计数减 1,直到计数为 0。

function findDuplicatesWithCount(arr1, arr2) {

 const map = new Map();

 const duplicates = [];

 // 记录第一个数组元素的出现次数

 for (const item of arr1) {

   map.set(item, (map.get(item) || 0) + 1);

 }

 // 遍历第二个数组,匹配并减少计数

 for (const item of arr2) {

   if (map.get(item) > 0) {

     duplicates.push(item);

     map.set(item, map.get(item) - 1);

   }

 }

 return duplicates;

}

比如数组 1 是 [2,2,3,3,3],数组 2 是 [2,2,2,3],它会返回 [2,2,3],正好是两个数组中元素重复次数的最小值。这种方法功能强大,但实现稍复杂,适合处理需要精确统计的场景,比如电商订单中的商品库存匹配。

六、方法对比:选对工具才是王道

为了帮你快速选择合适的方法,我整理了一张 “能力对比表”:

方法时间复杂度空间复杂度适用场景核心优势
双重循环O(n*m)O(1)极小型数组(n,m < 100)简单易理解
filter+includesO(n*m)O(1)小型数组(n,m < 1000)代码最简洁
SetO(n + m)O(n)大多数日常场景性能与简洁兼顾
排序双指针O(n log n + m log m)O(1)内存紧张的大型数组不占额外空间
Map 进阶版O(n + m)O(n)需要统计重复次数的场景功能全面

总结:随机应变对症下药

任何问题都没有一劳永逸的银弹

每个方法都像不同的工具:双重循环是 “小剪刀”,适合剪细线;Set 是 “瑞士军刀”,日常用途广;Map 进阶版是 “精密仪器”,能处理复杂需求。在实际开发中,不用追求最 “高级” 的方法,而是根据数据规模、内存限制和功能需求,选择最适合的方案。

下次再遇到 “找重复” 的问题,希望你能用出最合适的用法, 如果你还有其他有趣的实现方式,欢迎在评论区分享!

顺便分享一下我的个人微信小程序,康桑米达~~

小程序.png