350.两个数组的交集 II
题目
给你两个整数数数组 nums1
和 nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
*进阶*:
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果
nums1
的大小比nums2
小,哪种方法更优? - 如果
nums2
的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
解法
方法一:通过multiset+双指针法
题目要求找到两个数组的交集,并且交集中的元素需要考虑出现的次数。可以使用 multiset
数据结构来解决这个问题。
首先,代码中创建了两个 multiset
对象 mul1
和 mul2
,并将输入的 nums1
和 nums2
分别插入到这两个集合中。由于 multiset
允许重复元素,所以相同的元素会被正确地添加到集合中,并保持出现的次数。
然后,使用两个迭代器 it1
和 it2
来分别遍历 mul1
和 mul2
。在 while
循环中,首先比较迭代器指向的元素大小,如果 *it1 < *it2
,则说明 mul1
中的元素小于 mul2
中的元素,此时将 it1
向后移动一位。如果 *it1 > *it2
,则将 it2
向后移动一位。如果 *it1
和 *it2
相等,则将这个相同的元素添加到结果向量 result
中,并且同时将 it1
和 it2
向后移动一位。
最后,返回结果向量 result
,其中包含了两个数组的交集元素。
对于进阶问题:
- 如果给定的数组已经排好序,可以使用双指针的方法来优化算法。通过维护两个指针,分别指向
nums1
和nums2
的起始位置,根据元素的大小移动指针,可以避免创建额外的集合和迭代器。 - 如果
nums1
的大小比nums2
小,可以通过先将nums1
转换为multiset
,然后遍历nums2
,只处理在multiset
中出现过的元素,以减少内存消耗。 - 如果
nums2
的元素存储在磁盘上且内存有限,可以考虑使用分块处理的方法。将nums2
分成多个较小的块,在内存中逐块加载并与nums1
进行比较,然后将交集的部分存储下来。最后,将每个块的交集进行合并得到最终的结果。这样可以避免一次性加载所有的元素到内存中。
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
multiset<int> mul1(nums1.begin(),nums1.end());
multiset<int> mul2(nums2.begin(),nums2.end());
//此时mul1、mul2都是排好序的元素
vector<int> result;
auto it1 = mul1.begin(),it2 = mul2.begin();
while(it1 != mul1.end() && it2 != mul2.end()){
if(*it1 < *it2){
it1++;
}
else if(*it1 > *it2){
it2++;
}
else{
result.push_back(*it1);
it1++;
it2++;
}
}
return result;
}
};
方法二:通过map
首先,创建一个 map<int, int>
对象 m
来统计 nums1
中每个元素的出现次数。通过遍历 nums1
,使用自增操作符 m[num]++
来对元素 num
的出现次数进行累加。
然后,通过遍历 nums2
,使用 m.count(i)
来检查元素 i
是否存在于 m
中,以及 m[i]
是否大于 0,即元素 i
在 nums1
中出现的次数是否大于 0。这样可以确保交集中的元素在两个数组中都出现,并且按照较小的出现次数进行计数。如果满足条件,就将 m[i]
减 1,并将元素 i
添加到结果向量 result
中。
最后,返回结果向量 result
,其中包含了两个数组的交集元素。
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
map<int,int> m;
vector<int> result;
for(int num : nums1) m[num]++; //统计nums1中各个元素的数量
for(int i : nums2){
if(m.count(i) && m[i]){ //如果i在m中存在且m[i]>0的情况下(其实把前半个条件去掉也行)
m[i]--;
result.push_back(i);
}
}
return result;
}
};
一些拓展
两个数组的交集问题的应用
两个数组的交集问题在实际开发中有一些应用场景。下面是其中的一些例子:
数据库查询:在数据库查询中,我们经常需要对多个表进行联合查询,找到满足特定条件的交集记录。这个问题可以通过将数组中的元素作为查询条件,并将查询结果进行交集操作来解决。
好友推荐:在社交网络中,好友推荐是一个常见的功能。通过分析用户的兴趣、关注的人和活动等信息,可以找到与用户有相似兴趣的人,并将其推荐给用户。在这个过程中,可以使用两个数组的交集操作来找到共同关注的人或共同参加的活动。
数据分析:在数据分析中,我们经常需要对多个数据集进行合并和分析。通过找到两个数据集的交集,可以获取到共同的数据项,从而进行更准确的分析和预测。
优化思路:已排序数组的进一步优化
在题目的进阶部分,提到了如果给定的数组已经排好序,可以如何优化算法。
对于已排序的数组,我们可以使用双指针的方法来进一步优化算法。通过维护两个指针,分别指向 nums1 和 nums2 的起始位置,根据元素的大小移动指针,可以避免创建额外的集合和迭代器。
内存限制下的优化
在题目的进阶部分,还提到了如果 nums2 的元素存储在磁盘上,内存是有限的,并且不能一次性加载所有的元素到内存中,应该如何处理。
对于这种情况,可以考虑使用分块处理的方法。将 nums2 分成多个较小的块,在内存中逐块加载并与 nums1 进行比较,然后将交集的部分存储下来。最后,将每个块的交集进行合并得到最终的结果。这样可以避免一次性加载所有的元素到内存中,同时还能有效地处理大规模数据的交集问题。