leetcode350--- 两个数组的交集
leetcode350— 两个数组的交集
关键字:哈希表,双指针
题目
给定两个数组,编写一个函数来计算它们的交集。
示例 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 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii
方法
1.哈希表
- 新建一个列表用于储存交集的元素。
- 对数组1遍历建立字典,字典的键为数组的元素值,对应的值为该元素在字典中出现的次数。
- 遍历数组2,查找数组2的值是否在字典的键中出现且对应次数是否大于0,如果出现则加入列表并且将对应次数减1。
- 返回列表
代码如下:
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
hashmap = {}
result = []
for d in nums1:
if d in hashmap.keys():
hashmap[d] += 1
else:
hashmap[d] = 1
for j in nums2:
if j in hashmap.keys() and hashmap[j] > 0 :
result.append(j)
hashmap[j] -= 1
return result
复杂度分析
- 时间复杂度: O ( m + n ) O(m+n) O(m+n),需要遍历两个数组并进行操作。
- 空间复杂度: O ( min ( m , n ) ) O(\min(m,n)) O(min(m,n)), 对较短的数组进行哈希表的操作(后续改进后),哈希表的大小不会超过较短的数组的长度, 并且生成了储存结果的列表。
官方答案哈希表计数写法:
m = collections.Counter()
for num in nums1:
m[num] += 1
collections是Python内建的一个集合模块,提供了许多有用的集合类。以提供Python标准内建容器 dict , list , set , 和 tuple 的替代选择。
一个 Counter 是一个 dict 的子类,用于计数可哈希对象。它是一个集合,元素像字典键(key)一样存储,它们的计数存储为值。计数可以是任何整数值,包括0和负数。
所以使用这个Counter生成的对象可以使代码更简洁,提高一点速度。
hashmap = collections.Counter()
for num in nums1:
hashmap[num] += 1
并且判断时更方便:
for j in nums2:
if hashmap[j] > 0:
result.append(j)
hashmap[j] -= 1
再改进:
针对数组1,数组2中较短的一个建立哈希表可以更节省空间,因此在开头加入对两个数组长度的判断。
if len(nums1) > len(nums2):
return self.intersect(nums2, nums1)
改进后代码:
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
if len(nums1) > len(nums2):
return self.intersect(nums2, nums1)
hashmap = collections.Counter()
result = []
for num in nums1:
hashmap[num] += 1
for j in nums2:
if hashmap[j] > 0:
result.append(j)
hashmap[j] -= 1
return result
2.双指针法
-
对数组1,2先进行排序,从小到大排列,生成一个列表储存结果。
-
对于排序好的数组利用双指针进行比较。
初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,将该数字添加到列表,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
代码如下:
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1 = sorted(nums1)
nums2 = sorted(nums2)
i = 0
j = 0
result = []
n1 = len(nums1)
n2 = len(nums2)
while i < n1 and j < n2:
if nums1[i] == nums2[j]:
result.append(nums1[i])
i += 1
j += 1
elif nums1[i] > nums2[j]:
j += 1
else:
i += 1
return result
复杂度分析
- 时间复杂度: O ( m log m + n log n ) O(m\log m+n\log n) O(mlogm+nlogn),其中 m 和 n 分别是两个数组的长度。对两个数组进行排序的时间复杂度是 O ( m log m + n log n ) O(m \log m+n \log n) O(mlogm+nlogn),遍历两个数组的时间复杂度是 O ( m + n ) O(m+n) O(m+n),因此总时间复杂度是 O ( m log m + n log n ) O(m \log m+n \log n) O(mlogm+nlogn)。
- 空间复杂度: O ( min ( m , n ) ) O(\min(m,n)) O(min(m,n)),储存交集的列表长度最大为两者中最小值。
总结
对于进阶部分的问题:
- 当两个数组有序时,采取方法2可以省去排序时间,并与方法比较起来更节省空间。少一个哈希表的存储空间。
- 当内存有限,无法一次加载数组2时,就无法高效地对数组2进行排序,使用方法1更好,并且方法1的查询操作可以每次访问部分元素并处理即可。
- 同理,当两个数组大小相差过大时,使用改进后的方法1更节省时间。