1. 理解题目需求:合并两个数组的常见场景
在编程面试和日常开发中,合并两个数组是最基础也最常遇到的操作之一。力扣第2722题要求我们根据ID合并两个数组,这实际上模拟了现实开发中一个非常典型的场景——数据整合。
假设我们有两个数据源:
- 数组arr1包含用户ID和对应的用户名
- 数组arr2包含用户ID和对应的用户年龄
我们的任务是根据相同的ID,将这两个数组合并为一个包含完整用户信息(ID、用户名、年龄)的新数组。这种操作在前端处理API响应、后端整合数据库查询结果时非常常见。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题分析与边界条件考虑
2.1 输入数据结构分析
题目给出的两个数组结构如下:
javascript复制const arr1 = [
{"id": 1, "name": "John"},
{"id": 2, "name": "Mary"}
];
const arr2 = [
{"id": 1, "age": 30},
{"id": 3, "age": 25}
];
我们需要考虑以下几种边界情况:
- ID在两个数组中完全匹配(如id=1)
- ID只在arr1中存在(如id=2)
- ID只在arr2中存在(如id=3)
- 空数组输入
- 数组中包含重复ID的情况
2.2 合并策略选择
常见的合并策略有三种:
- 内连接(INNER JOIN):只保留两个数组中都存在的ID
- 左连接(LEFT JOIN):以arr1为基础,保留arr1中所有ID
- 全连接(FULL OUTER JOIN):保留两个数组中所有ID
根据题目描述,我们需要实现的是全连接合并,即最终结果应包含所有出现过的ID,缺失的属性应保留为null或undefined。
3. JavaScript实现方案
3.1 基础实现:使用Map数据结构
javascript复制function join(arr1, arr2) {
const map = new Map();
// 处理第一个数组
for (const obj of arr1) {
map.set(obj.id, {...obj});
}
// 处理第二个数组
for (const obj of arr2) {
if (map.has(obj.id)) {
const existing = map.get(obj.id);
map.set(obj.id, {...existing, ...obj});
} else {
map.set(obj.id, {...obj});
}
}
// 将Map转换为数组并按id排序
return Array.from(map.values()).sort((a, b) => a.id - b.id);
}
这个实现的时间复杂度是O(n + m),其中n和m分别是两个数组的长度,空间复杂度也是O(n + m)。
3.2 优化技巧:避免不必要的对象拷贝
在实际面试中,面试官可能会询问如何优化这个实现。我们可以通过直接修改对象来避免不必要的展开操作:
javascript复制function join(arr1, arr2) {
const map = new Map();
for (const obj of arr1) {
map.set(obj.id, obj);
}
for (const obj of arr2) {
if (map.has(obj.id)) {
Object.assign(map.get(obj.id), obj);
} else {
map.set(obj.id, obj);
}
}
return Array.from(map.values()).sort((a, b) => a.id - b.id);
}
注意:这种实现会直接修改原始数组中的对象,如果原始数组需要保持不变,应该使用第一种实现方式。
4. 测试用例设计
良好的测试用例应该覆盖所有边界情况:
javascript复制// 测试用例1:正常情况
const arr1 = [{"id":1,"name":"John"},{"id":2,"name":"Mary"}];
const arr2 = [{"id":1,"age":30},{"id":3,"age":25}];
console.log(join(arr1, arr2));
// 预期输出:[{"id":1,"name":"John","age":30},{"id":2,"name":"Mary"},{"id":3,"age":25}]
// 测试用例2:空数组
console.log(join([], [])); // []
// 测试用例3:一个数组为空
console.log(join([], [{"id":1,"age":30}])); // [{"id":1,"age":30}]
// 测试用例4:重复ID
console.log(join([{"id":1,"name":"John"},{"id":1,"name":"Jack"}], [{"id":1,"age":30}]));
// 取决于实现方式,可能输出[{"id":1,"name":"Jack","age":30}]或抛出错误
5. 实际应用场景扩展
5.1 前端API数据整合
在前端开发中,我们经常需要从不同API端点获取数据然后合并:
javascript复制async function fetchUserData() {
const [basicInfo, contactInfo] = await Promise.all([
fetch('/api/users/basic'),
fetch('/api/users/contact')
]);
const basicData = await basicInfo.json();
const contactData = await contactInfo.json();
return join(basicData, contactData);
}
5.2 数据库查询结果合并
在后端开发中,我们可能需要从不同表查询数据然后合并:
javascript复制// 伪代码示例
async function getUserProfile(userId) {
const [basicInfo, preferences] = await Promise.all([
db.query('SELECT * FROM users WHERE id = ?', [userId]),
db.query('SELECT * FROM user_prefs WHERE user_id = ?', [userId])
]);
return join(basicInfo, preferences.map(p => ({id: p.user_id, ...p})));
}
6. 性能优化与进阶思考
6.1 大数据量处理
当处理大规模数据时(如数万条记录),我们可以考虑:
- 使用更高效的数据结构,如专门的对象池
- 分批处理数据,避免内存溢出
- 使用Web Worker进行并行处理
6.2 多数组合并
如果需要合并多于两个数组,可以扩展我们的实现:
javascript复制function multiJoin(...arrays) {
const map = new Map();
for (const arr of arrays) {
for (const obj of arr) {
if (map.has(obj.id)) {
Object.assign(map.get(obj.id), obj);
} else {
map.set(obj.id, {...obj});
}
}
}
return Array.from(map.values()).sort((a, b) => a.id - b.id);
}
6.3 TypeScript类型安全实现
在TypeScript中,我们可以添加类型约束:
typescript复制interface Mergeable {
id: number;
[key: string]: any;
}
function join<T extends Mergeable, U extends Mergeable>(arr1: T[], arr2: U[]): Array<T & U> {
const map = new Map<number, T & U>();
for (const obj of arr1) {
map.set(obj.id, {...obj} as T & U);
}
for (const obj of arr2) {
if (map.has(obj.id)) {
Object.assign(map.get(obj.id)!, obj);
} else {
map.set(obj.id, {...obj} as T & U);
}
}
return Array.from(map.values()).sort((a, b) => a.id - b.id);
}
7. 常见错误与调试技巧
7.1 对象引用问题
一个常见的错误是直接使用原始数组中的对象引用:
javascript复制// 错误示例:会修改原始数组
function badJoin(arr1, arr2) {
const map = new Map();
for (const obj of arr1) {
map.set(obj.id, obj); // 直接使用原对象引用
}
// ...后续操作会修改原始arr1中的对象
}
7.2 ID类型不一致
有时ID可能是字符串或数字,需要统一处理:
javascript复制function safeJoin(arr1, arr2) {
const map = new Map();
const process = (obj) => {
const id = typeof obj.id === 'string' ? parseInt(obj.id) : obj.id;
return {...obj, id};
};
for (const obj of arr1) {
const processed = process(obj);
map.set(processed.id, processed);
}
// ...处理arr2
}
7.3 排序稳定性
当ID相同时,Array.sort()可能会产生不稳定结果。为了确保稳定排序:
javascript复制function stableJoin(arr1, arr2) {
// ...合并逻辑
return Array.from(map.values()).sort((a, b) => {
if (a.id === b.id) {
// 添加额外的排序条件
return (a.name || '').localeCompare(b.name || '');
}
return a.id - b.id;
});
}
8. 力扣刷题策略建议
- 先理解题目:花5分钟彻底理解题目要求和边界条件
- 手动模拟:用简单测试用例手动模拟合并过程
- 选择数据结构:根据问题特点选择合适的数据结构(本题适合用Map)
- 编写伪代码:先写步骤再实现细节
- 测试边界:特别关注空数组、重复ID等边界情况
- 优化思考:完成基本实现后考虑时间和空间优化
对于这类数组操作题目,建议平时多练习JavaScript数组的高阶函数(map、filter、reduce等),并熟悉ES6的Map/Set等数据结构。
