350. 两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。
解题思路: 本题虽然标记的是Easy,但是此题进阶第3点提的要求非常有趣,值得好好解一下。OK,先介绍磁盘内存不受限的解法,i.e.,所有要处理的数据均可一次加载到内存中,那么首先对两个序列继续排序,然后用双指针分别指向两个序列的元素,当元素相当时候,则找到交集中的一个元素,指针同时挪动,若两个元素不相等,那么移动较小元素对应的指针。解此题的关键是要想到排序+双指针,那如果在思维混沌时想到这两个组合呢,遇到思路一时阻塞的题,我们本能的会尝试手动模拟的方式解题,即,如何求序列交集,那么我们最自然想法是拿着一个序列中的元素,去另一个序列中找是否有与它相同的元素(双指针的思想),OK,那会有什么问题吗,算法时间复杂度为很高,i.e.,
O
(
n
2
)
O(n^2)
O(n2),算法的开销主要在查找,OK,我们学过当序列有序时,查找效率可以降低为
O
(
l
o
g
n
)
O(logn)
O(logn),至此,思路即可打开,然后我们想一下怎么使用双指针找有序序列的交集,设计**双指针算法的关键是,两个指针,每次移动时,轮到谁移动。**然后回到此题,在排序完毕后,利用双指针对每个序列只用遍历一次即可解题,当元素相当时,自然找到交集元素,当元素不相等时,我们移动较小的元素意味着,我们希望较小的元素变大后能与另一个较大的元素相当,以期找到一个交集元素。OK,请看代码。
// 将两个数组排序,然后用双指针解题
// Time: O(nlogn), space:O(1)
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
int i = 0, j = 0, n = nums1.size(), m = nums2.size();
vector<int> res;
while (i < n && j < m) {
if (nums1[i] == nums2[j]) {
res.push_back(nums1[i]);
++i, ++j;
} else if (nums1[i] > nums2[j]) ++j;
else ++i;
}
return res;
}
};
Follow up中提了一个有趣的要求,磁盘内存不足,该如何优化算法,其实这句话我们换个说法,题目要求我们不能一次将nums2数据全部加载到内存中,那么对应到上述题解,对nums2的排序就不能work了,那么我们需要采取迭代的解法,即每次从nums2中获取一个元素参与求交集。由于交集中元素的个数要与两个序列对应,因此已经求得的交集元素要在两个序列中删除,为了加速查找,这里使用了multiset(允许元素重复)。
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> res;
multiset<int> ms(nums1.begin(), nums1.end());
for (auto num : nums2) {
if (ms.count(num) != 0) {
res.push_back(num);
auto it = ms.find(num);
ms.erase(it);
}
}
return res;
}
};
—————————————
参考资料:
https://en.cppreference.com/w/cpp/container/multiset