LeetCode Notes_#350_两个数组的交集 II
Contents
题目
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
说明:
- 输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
- 我们可以不考虑输出结果的顺序。
进阶:
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果nums1的大小比nums2小很多,哪种方法更优?
- 如果nums2的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
解答
方法1:哈希表统计出现次数
比较笨的办法是统计两个数组的数字出现次数,然后遍历哈希表,对于交集的元素,将两个数组出现次数的较小值作为交集的出现次数。
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
List<Integer> res = new ArrayList<>();
HashMap<Integer, Integer> map1 = new HashMap<>();
HashMap<Integer, Integer> map2 = new HashMap<>();
for(int num : nums1){
map1.put(num, map1.getOrDefault(num, 0) + 1);
}
for(int num : nums2){
map2.put(num, map2.getOrDefault(num, 0) + 1);
}
for(int key: map1.keySet()){
if(map2.containsKey(key)){
int times = Math.min(map1.get(key), map2.get(key));
for(int i = 1;i <= times;i++){
res.add(key);
}
}
}
int[] resArray = new int[res.size()];
for(int i = 0;i <= res.size() - 1;i++){
resArray[i] = res.get(i);
}
return resArray;
}
}
BTW,这里最后为了返回int[]
类型,不可以使用toArray()
方法,因为这样只能得到Integer[]
类型的数组,最后反正还是得重新通过遍历赋值得到int[]
。
复杂度分析
时间复杂度:O(m + n)
,m和n分别是两个数组的长度
空间复杂度:O(m + n)
优化
其实不必要对于统计两个数组的元素出现频率,这样的空间复杂度有点高。可以只统计其中较小数组的频率。然后遍历另外一个数组,如果遇到哈希表存储的元素,就直接将其加入到交集数组中,然后将哈希表中这个元素的频次减去1。
这个方法对应于进阶里的第二问。但是我这里为了方便并没有写寻找较小数组的逻辑,默认统计nums1
的频率。
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
HashMap<Integer, Integer> map = new HashMap<>();
for(int num: nums1){
map.put(num, map.getOrDefault(num, 0) + 1);
}
List<Integer> res = new ArrayList<>();
for(int num: nums2){
if(map.containsKey(num) && map.get(num) != 0){
res.add(num);
map.put(num, map.get(num) - 1);
}
}
int[] resArray = new int[res.size()];
for(int i = 0; i <= resArray.length - 1;i++){
resArray[i] = res.get(i);
}
return resArray;
}
}
复杂度分析
时间复杂度:O(m + n)
,m和n分别是两个数组的长度
空间复杂度:O(m)
或O(n)
,如果使用较小数组来统计,则为O(min(m,n)
方法2:排序后使用双指针
这个对应于进阶当中的第一问,相当于使用双指针的方式,节省了空间复杂度。
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
List<Integer> res = new ArrayList<>();
int i = 0,j = 0;
while(i <= nums1.length - 1 && j <= nums2.length - 1){
if(nums1[i] < nums2[j]){
i++;
}
else if(nums1[i] == nums2[j]){
res.add(nums1[i]);
i++;
j++;
}else{
j++;
}
}
int[] resArray = new int[res.size()];
for(int k = 0;k <= res.size() - 1;k++){
resArray[k] = res.get(k);
}
return resArray;
}
}
复杂度分析
时间复杂度:O(mlogm + nlogn)
,排序两个数组的复杂度
空间复杂度:O(min(m, n))
,需要借助一个临时的ArrayList
变量
进阶问题3
对应进阶问题三,如果内存十分小,不足以将数组全部载入内存,那么必然也不能使用哈希这类费空间的算法,只能选用空间复杂度最小的算法,即排序+双指针法。
但还需要改造,一般说排序算法都是针对于内部排序,一旦涉及到跟磁盘打交道(外部排序),则需要特殊的考虑。归并排序是天然适合外部排序的算法,可以将分割后的子数组写到单个文件中,归并时将小文件合并为更大的文件。当两个数组均排序完成生成两个大文件后,即可使用双指针遍历两个文件(用指针遍历文件,而不需要将整个文件加载进入内存),如此可以使空间复杂度最低。