「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。
题目描述🌍
给你两个整数数组 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 <= 10000 <= nums1[i], nums2[i] <= 1000
进阶:
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果
nums1的大小比nums2小,哪种方法更优? - 如果
nums2的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
集合法🧩
解题思路
使用集合 List 而非 HashSet,因为 List 可包含重复元素;逐个判断 nums2 中的元素是否存在于 nums1,一旦发现存在,添加该元素到交集数组中,且在 nums1 中删除该元素,遍历完成后即可获得所求交集。
代码
Java
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
List<Integer> numsList = Arrays.stream(nums1).boxed().collect(Collectors.toList());
int[] intersection = new int[nums1.length];
int index = 0;
for (int i = 0; i < nums2.length; i++) {
// 判断nums1中是否包含nums2元素
if (numsList.contains(nums2[i])) {
// 移除nums1该元素
numsList.remove(new Integer(nums2[i]));
// 添加nums1与nums2交集元素
intersection[index++] = nums2[i];
}
}
// 返回交集部分
return Arrays.copyOfRange(intersection, 0, index);
}
}
C++
class Solution {
public:
vector<int> intersect(vector<int> &nums1, vector<int> &nums2) {
vector<int> intersect;
for (int num: nums2) {
auto pos = std::find(nums1.begin(), nums1.end(), num);
if (pos != nums1.end()) {
nums1.erase(pos);
intersect.push_back(num);
}
}
return intersect;
}
};
m, n 分别为
nums1,nums2的数组长度,下同
时间复杂度:
空间复杂度:
哈希计数法🔮
解题思路
因为同一个元素可能在数组中出现两次,所以我们可以借助哈希表来记录元素出现的次数,将长度较短的数组 nums1 转为哈希表(降低空间复杂度),然后遍历较长的数组 nums2,对于每一个元素,若存在于 nums1 中且对应次数 >0,则将该元素添加到交集数组中,并减少哈希表中对应的元素出现的次数。
图解来源于 LeetCode 官网
代码
Java
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
// 有可能减小空间复杂度
if (nums2.length < nums1.length) {
return intersect(nums2, nums1);
}
Map<Integer, Integer> hashMap = new HashMap<>();
int[] intersection = new int[nums1.length];
int index = 0;
// 等价于: Arrays.stream(nums1).boxed().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
for (int num : nums1) {
// getOrDefault: 如果key存在则获取对应的value, 不存在则返回0
int count = hashMap.getOrDefault(num, 0) + 1;
hashMap.put(num, count);
}
for (int num : nums2) {
// containsKey 判断: 以防 NullPointer; 也可用getOrDefault
if (hashMap.containsKey(num) && hashMap.get(num) > 0) {
intersection[index++] = num;
hashMap.put(num, hashMap.get(num) - 1);
}
}
return Arrays.copyOfRange(intersection, 0, index);
}
}
C++
class Solution {
public:
vector<int> intersect(vector<int> &nums1, vector<int> &nums2) {
if (nums2.size() < nums1.size())
return intersect(nums2, nums1);
unordered_map<int, int> counts;
vector<int> intersect;
for (int num: nums1) {
counts[num]++;
}
for (int num: nums2) {
if (counts.find(num) != counts.end() && counts[num] > 0) {
intersect.push_back(num);
counts[num]--;
}
}
return intersect;
}
};
时间复杂度:
空间复杂度:,哈希表的大小不会超过任一数组的长度,即不超过较短的那个数组的长度。
排序 & 双指针🥏
解题思路
排序双指针的思路在上一题中已经讲解清楚了:「LeetCode」349.两个数组的交集
而二者的区别在于元素是否可以重复,所以一旦找到相同元素后,两指针同时后移一位即可(无需像上一题跳过重复元素)。
代码
Java
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int length1 = nums1.length;
int length2 = nums2.length;
// 交集元素个数少于任一个数组的长度
int[] intersection = new int[length1];
int index1 = 0, index2 = 0, index = 0;
while (index1 < length1 && index2 < length2) {
if (nums1[index1] == nums2[index2]) {
// 将交集元素加入到新数组中, 所有指针同时向后移
intersection[index++] = nums1[index1];
index1++;
index2++;
} else if (nums1[index1] < nums2[index2]) {
index1++;
} else {
index2++;
}
}
// 只返回含交集元素的部分
return Arrays.copyOfRange(intersection, 0, index);
}
}
C++
class Solution {
public:
vector<int> intersect(vector<int> &nums1, vector<int> &nums2) {
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
int len1 = nums1.size(), len2 = nums2.size();
int i = 0, i1 = 0, i2 = 0;
vector<int> intersection;
while (i1 < len1 && i2 < len2) {
if (nums1[i1] < nums2[i2]) {
i1++;
} else if (nums1[i1] > nums2[i2]) {
i2++;
} else {
intersection.push_back(nums1[i1]);
++i1;
++i;
++i2;
}
}
return intersection;
}
};
时间复杂度:,两数组的排序时间为 ,双指针交错寻找交集元素所耗费时间为 。
空间复杂度:
进阶答疑👨💻
第一问
Q:如果给定的数组已经排好序呢?你将如何优化你的算法?
A:如果数组已排序,那就直接用双指针法(方法二),这下时间复杂度降到了 ,空间复杂度降为
第二问
Q:如果 nums1 的大小比 nums2 小,哪种方法更优?
A:哈希计数法
第三问
Q:如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
A:因为哈希计数法中开头代码做了点处理: 对 nums1 计数,用 nums2 查询,所以如果 nums2 存储在磁盘上,大可放心使用哈希计数法,因为 nums2 仅仅涉及查询,每次只需取一部分处理即可。而排序双指针法因为内存有限,无法对数组进行高效排序。
最后🌅
该篇文章为 「LeetCode」 系列的 No.22 篇,在这个系列文章中:
- 尽量给出多种解题思路
- 提供题解的多语言代码实现
- 记录该题涉及的知识点
👨💻争取做到 LeetCode 每日 1 题,所有的题解/代码均收录于 「LeetCode」 仓库,欢迎随时加入我们的刷题小分队!