🚩传送门:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/

题目

给定两个数组,编写一个函数来计算它们的交集

示例 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. 输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
  2. 我们可以不考虑输出结果的顺序。

进阶:

  1. 如果给定的数组已经排好序呢?你将如何优化你的算法?
  2. 如果 nums1 的大小比 nums2 小很多,哪种方法更优?
  3. 如果 nums2 的元素存储在磁盘上,内存是有限的,且你不能一次加载所有的元素到内存中,你该怎么办?

解题思路:哈希表

由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。

首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。

为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。
tnez5-0ns6q.gif

复杂度分析

时间复杂度:

其中 和 分别是两个数组的长度。需要遍历两个数组并对哈希表进行操作,哈希表操作的时间复
杂度是 ,因此总时间复杂度与两个数组的长度和呈线性关系。

空间复杂度:

其中 和 分别是两个数组的长度。对较短的数组进行哈希表的操作,哈希表的大小不会超过较短
的数组的长度。为返回值创建一个数组 intersection,其长度为较短的数组的长度。

官方代码

  1. class Solution {
  2. public int[] intersect(int[] nums1, int[] nums2) {
  3. //1.确保nums1短,nums2长
  4. if (nums1.length > nums2.length) {
  5. return intersect(nums2, nums1);
  6. }
  7. Map<Integer, Integer> map = new HashMap<Integer, Integer>();
  8. //2.遍历nums1,加入map,并计数
  9. for (int num : nums1) {
  10. //获取map中num的个数
  11. int count = map.getOrDefault(num, 0) + 1;
  12. //存入map
  13. map.put(num, count);
  14. }
  15. int[] intersection = new int[nums1.length];
  16. int index = 0;
  17. //3.遍历nums2
  18. for (int num : nums2) {
  19. //获取map中num的个数
  20. int count = map.getOrDefault(num, 0);
  21. if (count > 0) {
  22. //存在,存入公共集合
  23. intersection[index++] = num;
  24. count--;
  25. if (count > 0) {//大于0更新个数
  26. map.put(num, count);
  27. } else {//等于0移除元素
  28. map.remove(num);
  29. }
  30. }
  31. }
  32. //4.将intersection的[0,index)范围返回
  33. return Arrays.copyOfRange(intersection, 0, index);
  34. }
  35. }

解题思路:排序 + 双指针

如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。

首先对两个数组进行排序,然后使用两个指针遍历两个数组。

  1. 初始时,两个指针分别指向两个数组的头部。
  2. 每次比较两个指针指向的两个数组中的数字
    • 如果两个数字不相等,则将指向较小数字的指针右移一位
    • 如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。
    • 当至少有一个指针超出数组范围时,遍历结束。

复杂度分析

时间复杂度:

其中 和 分别是两个数组的长度。对两个数组进行排序的时间复杂度是 ,
遍历两个数组的时间复杂度是 ,因此总时间复杂度是 。

空间复杂度:

其中 和 分别是两个数组的长度。为返回值创建一个数组 intersection,其长度为较短的数组的
长度。不过在 C++ 中,我们可以直接创建一个 vector,不需要把答案临时存放在一个额外的数组
中,所以这种实现的空间复杂度为 。

官方代码

  1. class Solution {
  2. public int[] intersect(int[] nums1, int[] nums2) {
  3. //1.两个数组排序
  4. Arrays.sort(nums1);
  5. Arrays.sort(nums2);
  6. int length1 = nums1.length, length2 = nums2.length;
  7. int[] intersection = new int[Math.min(length1, length2)];
  8. int index1 = 0, index2 = 0, index = 0;
  9. //2.两个下标均未越界
  10. while (index1 < length1 && index2 < length2) {
  11. //3.nums1下标元素小,小的指针右移
  12. if (nums1[index1] < nums2[index2]) {
  13. index1++;
  14. } else if (nums1[index1] > nums2[index2]) {
  15. //4.nums1下标元素小,小的指针右移
  16. index2++;
  17. } else {
  18. intersection[index] = nums1[index1];
  19. //5.相等添加,集体右移
  20. index1++;
  21. index2++;
  22. index++;
  23. }
  24. }
  25. //6.将intersection的[0,index)范围返回
  26. return Arrays.copyOfRange(intersection, 0, index);
  27. }
  28. }

结语

如果 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中。那么就无法高效地对 进行排序,因此推荐使用方法一而不是方法二。在方法一中, 只关系到查询操作,因此每次读取 中的一部分数据,并进行处理即可。