js 中 如何获取数组的交集【面试题】
一、数组元素为基本类型
:Number、String、等基本类型时
1、使用 Set 和 filter(适用于两个数组)
const intersection = (arr1, arr2) => {
const set = new Set(arr2);
return [...new Set(arr1)].filter(item => set.has(item));
};
将第二个数组转为 Set
提高查询效率,然后遍历第一个数组并过滤出同时存在于 Set 中的元素。使用 [...new Set(arr1)]
确保第一个数组中的元素唯一。
2、处理多个数组的交集
const intersection = (...arrays) => {
if (arrays.length === 0) return [];
// 以第一个数组为基准,转为 Set 去重
let result = new Set(arrays[0]);
for (const arr of arrays.slice(1)) {
const currentSet = new Set(arr);
// 过滤出同时存在于当前数组的元素
result = new Set([...result].filter(item => currentSet.has(item)));
if (result.size === 0) break; // 提前终止无意义遍历
}
return [...result];
};
依次用每个数组的 Set
过滤基准集合,最终返回所有数组的共同元素。
const arr1 = [1, 2, 2, 3];
const arr2 = [2, 3, 4];
const arr3 = [3, 4, 5];
console.log(intersection(arr1, arr2)); // 输出: [2, 3]
console.log(intersection(arr1, arr2, arr3)); // 输出: [3]
时间复杂度优化:使用 Set
将查找操作降至 (1)O(1)
,整体效率较高。
元素唯一性:返回的交集元素始终唯一。
空数组处理:如果输入空数组或交集为空,直接返回空数组。
3、注意事项
严格相等:Set 使用 === 判断元素是否相同
,对象元素需额外处理。
兼容性:Set 和 filter
在现代浏览器中支持良好,若需支持旧环境(如 IE),需引入 Polyfill。
二、数组元素为引用类型
时:引用类型需要对比值,而不是内存中的地址
1、基于唯一标识符(推荐)
如果对象有唯一标识符(如 id)
,可以通过该属性快速判断是否属于交集:
const getObjectIntersection = (key, ...arrays) => {
if (arrays.length === 0) return [];
// 统计每个对象标识符出现的次数
const countMap = new Map();
arrays.forEach(arr => {
const uniqueItems = Array.from(new Set(arr.map(item => item[key])));
uniqueItems.forEach(id => {
countMap.set(id, (countMap.get(id) || 0) + 1);
});
});
// 筛选出现次数等于数组数量的标识符
const resultIds = Array.from(countMap.entries())
.filter(([_, count]) => count === arrays.length)
.map(([id]) => id);
// 返回第一个数组中符合条件的所有对象(保留原始数据)
return arrays[0].filter(item => resultIds.includes(item[key]));
};
// 示例
const arr1 = [{ id: 1, name: 'A' }, { id: 2, name: 'B' }];
const arr2 = [{ id: 2, name: 'B' }, { id: 3, name: 'C' }];
const arr3 = [{ id: 2, name: 'B' }, { id: 4, name: 'D' }];
console.log(getObjectIntersection('id', arr1, arr2, arr3));
// 输出: [{ id: 2, name: 'B' }]
2、通用对象比较(深比较)
如果对象没有唯一标识符,或需要比较所有属性,可以使用 深比较 方法:
const deepEqual = (obj1, obj2) => {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || !obj1 || !obj2) return false;
const keys1 = Object.keys(obj1), keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
return keys1.every(key => keys2.includes(key) && deepEqual(obj1[key], obj2[key]));
};
const getObjectIntersection = (...arrays) => {
return arrays.reduce((acc, currentArr) => {
return acc.filter(accItem =>
currentArr.some(currentItem => deepEqual(accItem, currentItem))
);
});
};
// 示例
const obj1 = { id: 1, data: { value: 'X' } };
const obj2 = { id: 1, data: { value: 'X' } }; // 内容相同但引用不同
const arr1 = [obj1, { id: 2 }];
const arr2 = [obj2, { id: 2 }];
console.log(getObjectIntersection(arr1, arr2));
// 输出: [{ id: 2 }](obj1 和 obj2 内容相同但引用不同,未被识别为相同)
3、序列化比较(JSON.stringify)
如果对象结构简单且无循环引用,可通过序列化简化比较:
const getObjectIntersection = (...arrays) => {
if (arrays.length === 0) return [];
// 序列化所有对象
const serializedArrays = arrays.map(arr => [
...new Set(arr.map(item => JSON.stringify(item)))
]);
// 统计序列化后的字符串出现次数
const countMap = new Map();
serializedArrays.forEach(arr => {
arr.forEach(str => {
countMap.set(str, (countMap.get(str) || 0) + 1);
});
});
// 筛选出现次数等于数组数量的字符串
const commonStrings = Array.from(countMap.entries())
.filter(([_, count]) => count === arrays.length)
.map(([str]) => str);
// 反序列化并返回第一个数组中匹配的对象
return arrays[0].filter(item =>
commonStrings.includes(JSON.stringify(item))
);
};
4、注意事项
性能问题:深比较和序列化
在大数据量时性能较差
,优先使用唯一标识符方案。
对象引用:如果多个数组中的对象实际上是同一个引用
,直接使用 Set
即可。
嵌套对象:深比较需要递归处理嵌套对象,确保所有层级属性
一致。
特殊类型:JSON 序列化会丢失 undefined、函数和循环引用
,需谨慎使用。