349. 两个数组的交集
题意:给定两个数组,编写一个函数来计算它们的交集。
说明: 输出结果中的每个元素一定是唯一的。 我们可以不考虑输出结果的顺序。
思路
这道题目,主要要学会使用一种哈希数据结构:unordered_set,这个数据结构可以解决很多类似的问题。
注意题目特意说明:输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序
这道题用暴力的解法时间复杂度是O(n^2),那来看看使用哈希法进一步优化。
那么用数组来做哈希表也是不错的选择,例如242. 有效的字母异位词(opens new window)
但是要注意,使用数组来做哈希的题目,是因为题目都限制了数值的大小。
而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
此时就要使用另一种数据结构了,Set
。关于 Set
,Java 提供了多种可用的实现类,例如:
HashSet
LinkedHashSet
TreeSet
其中,TreeSet
的底层实现是红黑树(基于 NavigableMap
,通常是 Red-Black tree
),而 HashSet
和 LinkedHashSet
的底层实现是哈希表(HashMap
)。
使用 HashSet
读写效率是最高的,它不需要对数据进行排序,并且天然保证元素的唯一性(不允许重复)。因此,在不需要维护元素顺序且追求最高性能的场景下,选择 HashSet
是最合适的。
补充说明:
HashSet
: 基于哈希表实现,提供最佳的平均时间复杂度(O(1))进行添加、删除和查找操作。不保证元素的迭代顺序。LinkedHashSet
: 继承自HashSet
,在哈希表的基础上增加了一个双向链表来维护插入顺序。因此,它保证了元素的迭代顺序与插入顺序一致,同时保持了接近HashSet
的性能(O(1) 平均时间)。TreeSet
: 基于红黑树(一种自平衡二叉查找树)实现。它保证了元素按照其自然顺序(或提供的Comparator
)进行排序。添加、删除和查找操作的时间复杂度为 O(log n),比哈希表实现慢,但能提供有序的集合视图。
思路如c++图所示:
暴力:
// 定义一个名为 Solution 的类
class Solution {
// 声明一个公共的实例方法 intersection,接收两个整数数组 nums1 和 nums2 作为参数,返回一个整数数组
// 该方法旨在找出两个数组的交集(元素出现在两个数组中)
public int[] intersection(int[] nums1, int[] nums2) {
// 创建一个长度为 1001 的整型数组 hash1,用于记录 nums1 中每个数字出现的次数
// 索引代表数字本身(0 到 1000),值代表该数字在 nums1 中出现的次数
int[] hash1 = new int[1001];
// 创建一个长度为 1001 的整型数组 hash2,用于记录 nums2 中每个数字出现的次数
// 索引代表数字本身(0 到 1000),值代表该数字在 nums2 中出现的次数
int[] hash2 = new int[1001];
// 使用增强 for 循环遍历 nums1 数组中的每一个元素
for(int i :nums1){
// 将当前元素 i 作为索引,将 hash1 数组中对应位置的计数加 1
// 这样就统计了 nums1 中每个数字的出现频次
hash1[i]++;
}
// 使用增强 for 循环遍历 nums2 数组中的每一个元素
for(int i :nums2){
// 将当前元素 i 作为索引,将 hash2 数组中对应位置的计数加 1
// 这样就统计了 nums2 中每个数字的出现频次
hash2[i]++;
}
// 创建一个 ArrayList 来动态存储交集元素
// 使用 List<Integer> 是因为我们事先不知道交集元素的个数
List<Integer> resList = new ArrayList<>();
// 使用普通 for 循环遍历 hash1 和 hash2 数组的索引(即数字 0 到 1000)
for(int i=0;i<1001;i++){
// 检查当前索引 i(代表数字 i)是否同时在 nums1 和 nums2 中出现过
// hash1[i] > 0 表示数字 i 在 nums1 中至少出现一次
// hash2[i] > 0 表示数字 i 在 nums2 中至少出现一次
if(hash1[i]>0 && hash2[i]>0){
// 如果数字 i 同时存在于两个数组中,则将其添加到结果列表 resList 中
// 注意:这里只添加一次,即使一个数字在原数组中出现多次,交集也只包含一个
resList.add(i);
}
}
// 初始化一个索引变量 index,用于追踪结果数组 res 中下一个要填充的位置
int index = 0;
// 创建一个长度等于结果列表 resList 大小的整型数组 res,用于存储最终的交集结果
int res[] = new int[resList.size()];
// 使用增强 for 循环遍历结果列表 resList 中的每一个元素
for(int i : resList){
// 将当前元素 i 赋值给结果数组 res 的 index 位置
res[index++]=i;
// 索引 index 自增 1,指向数组中的下一个位置
}
// 返回最终的交集数组 res
return res;
}
// 方法结束
}
// 类结束
- 时间复杂度:O(m + n + 1001) ≈ O(m + n),其中 m 和 n 分别是
nums1
和nums2
的长度。遍历两个数组和长度为 1001 的哈希数组。 - 空间复杂度:O(1001 + k) ≈ O(1),其中 k 是交集元素的个数。两个固定大小的哈希数组和一个存储结果的动态列表。由于哈希数组大小是固定的 (1001),通常认为是常数空间,但严格来说与输入范围有关。
使用hash:
// 定义一个名为 Solution 的类
class Solution {
// 声明一个公共的实例方法 intersection,接收两个整数数组 nums1 和 nums2 作为参数,返回一个整数数组
// 该方法旨在找出两个数组的交集(元素出现在两个数组中)
public int[] intersection(int[] nums1, int[] nums2) {
// 检查输入的两个数组是否为 null 或者长度为 0(空数组)
// 如果满足任一条件,说明无法计算交集或交集为空
if(nums1 ==null || nums1.length == 0 || nums2 ==null || nums2.length == 0 ){
// 直接返回一个长度为 0 的新数组,表示空的交集
return new int[0];
}
// 创建一个 HashSet,命名为 set,用于存储 nums1 数组中的所有**唯一**元素
// HashSet 特性:自动去重,查找元素的时间复杂度平均为 O(1)
Set<Integer> set = new HashSet<>();
// 创建另一个 HashSet,命名为 resSet,用于存储最终的交集元素
// 同样利用 HashSet 的去重特性,确保结果中没有重复元素
Set<Integer> resSet = new HashSet<>();
// 使用增强 for 循环遍历 nums1 数组中的每一个元素
for(int i : nums1){
// 将当前元素 i 添加到 set 集合中
// 如果 i 已经存在,add 方法会忽略(保证唯一性)
set.add(i);
}
// 使用增强 for 循环遍历 nums2 数组中的每一个元素
for(int i : nums2){
// 检查当前元素 i 是否存在于 set 集合中(即是否在 nums1 中出现过)
// set.contains(i) 利用哈希表查找,效率很高
if(set.contains(i)){
// 如果 i 同时存在于 nums1 (通过 set 判断) 和 nums2 中
// 则将其添加到结果集合 resSet 中
// HashSet 会自动处理重复添加的情况
resSet.add(i);
}
}
// 创建一个整型数组 res,其长度等于结果集合 resSet 的大小
// 这样可以精确地分配所需空间,避免浪费
int[] res = new int[resSet.size()];
// 初始化一个索引变量 index,用于追踪结果数组 res 中下一个要填充的位置
int index = 0;
// 使用增强 for 循环遍历结果集合 resSet 中的每一个元素
for(int i : resSet){
// 将当前元素 i 赋值给结果数组 res 的 index 位置
// 然后将 index 的值增加 1 (index++ 是后缀自增操作)
res[index++] = i;
}
// 返回最终的交集数组 res
return res;
}
// 方法结束
}
// 类结束
- 时间复杂度:O(m + n),其中 m 和 n 分别是
nums1
和nums2
的长度。遍历nums1
构建set
(O(m)),遍历nums2
并检查set
(O(n),每次contains
平均 O(1)),最后遍历resSet
构建结果数组 (O(k),k 是交集大小,k <= min(m, n))。总体是 O(m + n)。 - 空间复杂度:O(m + k),其中 m 是
nums1
中唯一元素的个数(存储在set
中),k 是交集元素的个数(存储在resSet
和最终数组res
中)。set
的空间是主要开销。