数组找重复值: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+includes | O(n*m) | O(1) | 小型数组(n,m < 1000) | 代码最简洁 |
| Set | O(n + m) | O(n) | 大多数日常场景 | 性能与简洁兼顾 |
| 排序双指针 | O(n log n + m log m) | O(1) | 内存紧张的大型数组 | 不占额外空间 |
| Map 进阶版 | O(n + m) | O(n) | 需要统计重复次数的场景 | 功能全面 |
总结:随机应变对症下药
任何问题都没有一劳永逸的银弹
每个方法都像不同的工具:双重循环是 “小剪刀”,适合剪细线;Set 是 “瑞士军刀”,日常用途广;Map 进阶版是 “精密仪器”,能处理复杂需求。在实际开发中,不用追求最 “高级” 的方法,而是根据数据规模、内存限制和功能需求,选择最适合的方案。
下次再遇到 “找重复” 的问题,希望你能用出最合适的用法, 如果你还有其他有趣的实现方式,欢迎在评论区分享!
顺便分享一下我的个人微信小程序,康桑米达~~