一、题目
给定两个数组,编写一个函数来计算它们的交集。
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
说明:
1、输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
2、我们可以不考虑输出结果的顺序。
进阶:
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果 nums1 的大小比 nums2 小很多,哪种方法更优?
- 如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有 的元素到内存中,你该怎么办?
二、分析
暴力就不写了,没啥技术含量。说下关键:在枚举时,外层为长数组,内层为短数组。
方法一:Map 计数
用 Map 的 key 记录第一个数组中的元素的值,value 记录出现的次数。
class Solution {
public:
vector<int> intersect(vector<int>& A, vector<int>& B) {
vector<int> ans;
unordered_map<int, int> m1, m2;
for (int a : A) m1[a]++;
for (int b : B) m2[b]++;
for (int a : A) if (m1[a] && m2[a]) {
int mi = min(m1[a], m2[a]);
while (mi--) {
ans.push_back(a);
m1.erase(a), m2.erase(a);
}
}
return ans;
}
};
优化:不是用两个 map,而是用减法遍历第二个数组,如果找到对应元素 && 对应 HashMap 中的 value 不为 0,则添加这个元素到 list 中。同时,HashMap 中的 value 值减一,表示已经找到 1 个相同的数。
class Solution {
public:
vector<int> intersect(vector<int>& A, vector<int>& B) {
vector<int> ans;
unordered_map<int, int> m;
for (int a : A) m[a]++;
for (int b : B) if (m[b]) {
ans.push_back(b);
m[b]--;
}
return ans;
}
};
复杂度分析
- 时间复杂度: O ( s i z e 1 + s i z e 2 ) O(size1 + size2) O(size1+size2)。
- 空间复杂度: O ( m i n ( s i z e 1 + s i z e 2 ) ) O(min(size1 + size2)) O(min(size1+size2)),我们对较小的数组进行哈希映射使用的空间。
方法二:双指针
思路
- 如果数组排好序,那么我采取双指针 p 1 、 p 2 p1、p2 p1、p2 对两个数组逐个遍历.
- 如果找到相同的元素,那么指针
p1、p2
都移到下一个位置. - 相比之下,如果数组1的值
nums1[p1]
>nums2[p2]
,那么我们可以做出判断–> “要找的元素” 应该在此刻 nums1 元素的后面。所以我们只让p1
移动。相反亦然。
复杂度分析
- 时间复杂度: O ( m a x ( s i z e 1 l o g s i z e 1 , s i z e 2 l o g s i z e 2 ) O(max(size1\ log\ size1,size2\ log\ size2) O(max(size1 log size1,size2 log size2)。
- 空间复杂度: O ( m i n ( s i z e 1 + s i z e 2 ) ) O(min(size1 + size2)) O(min(size1+size2))。
进阶:数组在磁盘中
思路
如果内存十分小,不足以将数组全部载入内存,那么必然也不能使用哈希这类费空间的算法。
一般说排序算法都是针对于内部排序,一旦涉及到磁盘(外部排序)。归并排序是天然适合外部排序的算法,可以将分割后的子数组写到单个文件中,归并时将小文件合并为更大的文件。 当两个数组均排序完成生成两个大文件后,即可使用双指针遍历两个文件,如此可以使空间复杂度最低。
关于外部排序与 JOIN,强烈推荐大家看一下 数据库内核杂谈(六):表的 JOIN(连接)这一系列数据库相关的文章