【力扣】2722. 根据 ID 合并两个数组
文章目录
- [【力扣】2722. 根据 ID 合并两个数组](#【力扣】2722. 根据 ID 合并两个数组)
题目
现给定两个数组 arr1 和 arr2 ,返回一个新的数组 joinedArray 。两个输入数组中的每个对象都包含一个 id 字段。
joinedArray 是一个通过 id 将 arr1 和 arr2 连接而成的数组。joinedArray 的长度应为唯一值 id 的长度。返回的数组应按 id 升序 排序。
如果一个 id 存在于一个数组中但不存在于另一个数组中,则该对象应包含在结果数组中且不进行修改。
如果两个对象共享一个 id ,则它们的属性应进行合并:
- 如果一个键只存在于一个对象中,则该键值对应该包含在对象中。
- 如果一个键在两个对象中都包含,则
arr2中的值应覆盖arr1中的值。
示例 1:
输入:
arr1 = [
{"id": 1, "x": 1},
{"id": 2, "x": 9}
],
arr2 = [
{"id": 3, "x": 5}
]
输出:
[
{"id": 1, "x": 1},
{"id": 2, "x": 9},
{"id": 3, "x": 5}
]
解释:没有共同的 id,因此将 arr1 与 arr2 简单地连接起来。
示例 2:
输入:
arr1 = [
{"id": 1, "x": 2, "y": 3},
{"id": 2, "x": 3, "y": 6}
],
arr2 = [
{"id": 2, "x": 10, "y": 20},
{"id": 3, "x": 0, "y": 0}
]
输出:
[
{"id": 1, "x": 2, "y": 3},
{"id": 2, "x": 10, "y": 20},
{"id": 3, "x": 0, "y": 0}
]
解释:id 为 1 和 id 为 3 的对象在结果数组中保持不变。id 为 2 的两个对象合并在一起。arr2 中的键覆盖 arr1 中的值。
示例 3:
输入:
arr1 = [
{"id": 1, "b": {"b": 94},"v": [4, 3], "y": 48}
]
arr2 = [
{"id": 1, "b": {"c": 84}, "v": [1, 3]}
]
输出: [
{"id": 1, "b": {"c": 84}, "v": [1, 3], "y": 48}
]
解释:具有 id 为 1 的对象合并在一起。对于键 "b" 和 "v" ,使用 arr2 中的值。由于键 "y" 只存在于 arr1 中,因此取 arr1 的值。
提示:
arr1 和 arr2 都是有效的 JSON 数组在 arr1 和 arr2 中都有唯一的键值 id2 <= JSON.stringify(arr1).length <= 1062 <= JSON.stringify(arr2).length <= 106
解决方案
概述
我们需要基于它们的 "id" 键合并两个数组 arr1 和 arr2。合并后的数组 joinedArray 应包含来自两个数组的所有唯一对象,并按其 id 值的升序排序。当对象具有相同的 id 时,它们的属性应合并,arr2 中的值将覆盖 arr1 中的值。如果一个 id 只存在于一个数组中,那么该 id 对应的对象应无修改地包含在结果中。
一些实际应用场景:
- 数据集成: 在集成来自多个来源的数据时,每个来源可能使用单独的数组提供具有公共 ID 的数据。根据 ID 合并这些数组允许将数据组合和整合到一个单一的数据集中,以供分析或处理。
- 社交媒体分析: 在分析社交媒体数据时,根据用户或帖子 ID 合并数组可以汇集相关信息,如用户资料、评论、点赞和分享。这使得可以进行综合分析、情感分析、识别热门帖子或发现用户行为中的模式。
- 地理信息系统(GIS): 在 GIS 应用中,基于位置 ID 或地理标识符合并数组可以实现空间数据的集成。这可能涉及合并包含地理特征、属性和其他相关信息的数组,从而促进空间分析、制图和决策制定。
- 供应链管理: 在供应链系统中,基于唯一标识符(如产品代码或订单 ID)合并数组允许跟踪和管理货物或服务的流动。合并数组有助于整合供应链不同阶段的信息,如采购、生产、分销和交付,从而实现有效的监控和优化。
方法 1:暴力法
- 我们首先创建一个新数组
combinedArray,通过组合 arr1 和 arr2 的内容来确保包含来自两个数组的所有对象。 - 接下来,我们初始化一个名为 merged 的空对象。该对象将用作容器,以存储基于其 ID 作为键的合并对象。
- 然后,我们使用
forEach方法迭代combinedArray中的每个对象。对于每个对象,我们检查它的 ID 是否已存在于 merged 对象中作为键。 - 如果 ID 在 merged 对象中不存在,我们将向 merged 对象添加一个新的键值对。键是 ID,值是通过创建当前对象的副本 (...obj) 而生成的新对象。这确保了 merged 对象中的每个对象都有自己独立的副本。
- 但是,如果 ID 已经存在于 merged 对象中,这意味着已经存在具有相同 ID 的另一个对象。在这种情况下,我们执行属性的合并。我们通过复制其属性 (...merged[id]) 并然后用当前对象的属性 (...obj) 覆盖它们来更新 merged 中的现有对象。这确保了合并过程中 arr2 中的属性优先于 arr1。
- 在合并所有对象之后,我们使用
Object.values()从 merged 对象中提取值。这会创建一个数组joinedArray,其中只包含已合并对象,而不包含 ID 键。 - 最后,我们使用基于 ID 键的升序对
joinedArray进行排序。我们使用 sort 方法和比较函数 (a, b) => a.id - b.id 来确定正确的排序顺序。比较函数比较两个对象的 ID 并确定它们在排序数组中的位置。 - 最终,我们将排序后的
joinedArray作为 join 函数的最终结果返回。
实现
js
/**
* @param {Array} arr1
* @param {Array} arr2
* @return {Array}
*/
var join = function(arr1, arr2) {
const combinedArray = arr1.concat(arr2);
const merged = {};
combinedArray.forEach((obj) => {
const id = obj.id;
if (!merged[id]) {
merged[id] = { ...obj };
} else {
merged[id] = { ...merged[id], ...obj };
}
});
const joinedArray = Object.values(merged);
joinedArray.sort((a, b) => a.id - b.id);
return joinedArray;
};
复杂度分析
- 时间复杂度:由于排序函数的存在,时间复杂度为
O(nlogn),其中 n 是combinedArray中的元素总数(arr1 的长度加上 arr2 的长度)。迭代和合并过程也会对时间复杂度有所贡献,但排序操作占主导地位。 - 空间复杂度:空间复杂度为 O(n),其中 n 是
combinedArray中的元素总数。这是因为创建了一个新数组(combinedArray)和一个新对象(merged),每个都可能存储来自 arr1 和 arr2 的所有元素。
方法 2:使用 Map
概述
我们可以使用 Map 来合并两个数组 arr1 和 arr2。我们将 arr1 中的所有对象添加到 Map 中,然后根据它们的 ID 合并 arr2 中的对象。合并后的对象存储在名为 res 的数组中。最后,我们根据 ID 属性将 res 数组按升序排序。
算法
- 我们首先创建一个新的 Map 对象,命名为 map。Map 用于高效地存储和检索键-值对。
- 使用 for-of 循环,我们遍历 arr1 中的每个对象。对于每个对象,我们将其 ID 设置为键,将整个对象设置为 map 中的值。
- 接下来,我们使用另一个 for-of 循环来遍历 arr2 中的每个对象。对于每个对象,我们检查其 ID 是否已经存在于 map 中。
- 如果该 ID 不存在于 map 中,我们将该 ID 设置为键,将整个对象设置为 map 中的值。这确保了在不修改的情况下将对象包含在 res 数组中。
- 但是,如果 ID 已经存在于 map 中,我们使用
map.get(obj.id)获取现有对象。然后,我们使用Object.keys(obj)来迭代当前对象的每个属性。 - 对于每个属性,我们使用当前对象的值更新现有对象的相应属性。此合并过程确保当对象具有相同的 ID 时,arr2 中的值将覆盖 arr1 中的值。
- 在合并所有对象后,我们创建一个名为 res 的空数组,用于存储最终结果。
- 我们使用
map.keys()来迭代 map 的键。对于每个键,我们使用map.get(key)检索相应的对象,并将其推送到 res 数组中。 - 最后,我们使用 sort 方法和比较函数 (a, b) => a.id - b.id 来根据 ID 属性的升序对 res 数组进行排序。
- 最终,我们将排序后的 res 数组作为 join 函数的最终结果返回。
实现
js
/**
* @param {Array} arr1
* @param {Array} arr2
* @return {Array}
*/
var join = function(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)) map.set(obj.id, obj);
else {
const prevObj = map.get(obj.id);
for (const key of Object.keys(obj)) prevObj[key] = obj[key];
}
}
const res = new Array();
for (let key of map.keys()) res.push(map.get(key));
return res.sort((a,b)=>a.id-b.id);
};
复杂度分析
- 时间复杂度:由于排序函数的存在,时间复杂度为
O(nlogn),其中 n 是combinedArray中的元素总数(arr1 的长度加上 arr2 的长度)。迭代和合并过程也会对时间复杂度有所贡献,但排序操作占主导地位。 - 空间复杂度:空间复杂度为 O(n),其中 n 是 map 中的元素总数。
方法 3:使用双指针
概述
主要思路类似于合并两个已排序数组。我们遍历已排序的 arr1 和 arr2,比较它们的 ID 并将对象添加到结果的 joinedArray 中,然后根据需要增加 i 或 j 指针。一旦一个数组被完全处理,就插入另一个数组中的剩余对象。
算法
- 我们首先根据其 ID 对 arr1 和 arr2 进行升序排序。这确保在合并过程中以一致的顺序处理对象。
- 初始化一个名为
joinedArray的空数组,用于存储合并的对象。 - 我们使用两个指针 i 和 j,来跟踪当前在 arr1 和 arr2 中的位置。我们从 i = 0 和 j = 0 开始。
- 进入一个 while 循环,直到达到 arr1 或 arr2 的末尾。在循环内部,我们比较当前位置的对象的 ID(arr1[i].id 和 arr2[j].id)。
- 如果 arr1 中的 ID 小于 arr2 中的 ID,我们将 arr1 中的对象添加到
joinedArray,并递增 i,以继续处理 arr1 中的下一个对象。 - 如果 arr1 中的 ID 大于 arr2 中的 ID,我们将 arr2 中的对象添加到
joinedArray,并递增 j,以继续处理 arr2 中的下一个对象。 - 如果 ID 相同,我们合并这两个对象的属性。我们通过将 arr1[i] 的属性展开(...arr1[i])然后用 arr2[j] 的相应值覆盖来创建一个新对象。合并的对象添加到
joinedArray,然后递增 i 和 j,以继续处理两个数组中的下一个对象。 - 在 while 循环结束后,我们检查是否还有未处理的对象在 arr1 或 arr2 中。如果有,我们进入单独的 while 循环,将剩余的对象添加到
joinedArray。 - 最后,我们返回
joinedArray,其中包含来自两个数组的所有合并对象。
实现
js
/**
* @param {Array} arr1
* @param {Array} arr2
* @return {Array}
*/
var join = function(arr1, arr2) {
arr1.sort((a,b) => a.id - b.id)
arr2.sort((a,b) => a.id - b.id)
let i = 0
let j = 0
const joinedArray = []
while(i < arr1.length && j < arr2.length) {
if(arr1[i].id === arr2[j].id) {
joinedArray.push({...arr1[i], ...arr2[j]})
i++
j++
continue
}
if(arr1[i].id < arr2[j].id) {
joinedArray.push({...arr1[i]})
i++
continue
}
joinedArray.push({...arr2[j]})
j++
}
while(i < arr1.length) {
joinedArray.push({...arr1[i]})
i++
}
while(j < arr2.length) {
joinedArray.push({...arr2[j]})
j++
}
return joinedArray
};
复杂度分析
- 时间复杂度:
arr.sort的时间复杂度为O(nlogn),其中 n 是最大数组的长度。 - 空间复杂度:由于我们创建了一个
joinedArray来存储结果,其大小可能与 arr1 和 arr2 的大小一样大,因此可以说空间复杂度为 O(n),其中 n 是 arr1 和 arr2 中元素的总数。但实际上,我们只考虑辅助空间(不包括输入和输出),真正的空间复杂度为 O(1)。这是因为算法的附加空间用于变量和指针(i 和 j),不随输入数组的大小而改变。
面试提示
- 对结果数组排序的目的是什么?
- 对结果数组按 ID 升序排序是为了确保对象以特定的顺序排列。这使得能够根据其 id 值更容易定位和检索对象,特别是用于进一步处理或分析时。
- 合并过程是否可以在原始数组上进行就地修改?
- 通常建议在合并过程中避免修改原始数组,以保持不可变性并避免意外的副作用。在合并过程中修改原始数组可能会引入复杂性,特别是如果数组在多个上下文中共享或用于多个目的。通常更好的做法是创建一个新的合并数组,以确保数据完整性并保持原始数组的原始状态。
- 编程中为什么合并基于共同键的数组很有用?
- 在编程中,基于共同键合并数组是常见的操作,特别是在处理关系型或结构化数据时。它允许我们将来自多个来源的信息组合和整合,从而促进数据分析、数据处理和数据集成。通过根据共享键合并数组,我们可以汇集相关数据,建立关系,并在合并数据集上执行进一步的操作。