双指针题目:两个数组的交集 II

文章目录

题目

标题和出处

标题:两个数组的交集 II

出处:350. 两个数组的交集 II

难度

3 级

题目描述

要求

给定两个数组 nums1 \texttt{nums1} nums1 和 nums2 \texttt{nums2} nums2,返回两个数组的交集。结果中的每个元素的出现次数必须是该元素在两个数组中同时出现的次数,可以按任意额顺序返回结果。

示例

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2] \texttt{nums1 = [1,2,2,1], nums2 = [2,2]} nums1 = [1,2,2,1], nums2 = [2,2]

输出: [2,2] \texttt{[2,2]} [2,2]

示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] \texttt{nums1 = [4,9,5], nums2 = [9,4,9,8,4]} nums1 = [4,9,5], nums2 = [9,4,9,8,4]

输出: [9,4] \texttt{[9,4]} [9,4]

解释: [4,9] \texttt{[4,9]} [4,9] 也是正确答案。

数据范围

  • 1 ≤ nums1.length, nums2.length ≤ 1000 \texttt{1} \le \texttt{nums1.length, nums2.length} \le \texttt{1000} 1≤nums1.length, nums2.length≤1000
  • 0 ≤ nums1[i], nums2[i] ≤ 1000 \texttt{0} \le \texttt{nums1[i], nums2[i]} \le \texttt{1000} 0≤nums1[i], nums2[i]≤1000

进阶

  • 如果给定的数组已经排好序,你将如何优化你的算法?
  • 如果 nums1 \texttt{nums1} nums1 的大小比 nums2 \texttt{nums2} nums2 小,哪种算法更优?
  • 如果 nums2 \texttt{nums2} nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,应该使用哪种算法?

解法一

思路和算法

两个数组的交集为在两个数组中都出现的元素集合。由于结果中需要考虑每个元素在两个数组中同时出现的次数,因此需要记录每个元素的出现次数。

首先遍历第一个数组,使用哈希表记录第一个数组中每个元素的出现次数。然后遍历第二个数组,对于第二个数组中的元素 num \textit{num} num,如果该元素在哈希表中的出现次数大于 0 0 0,则该元素在两个数组的交集内,将该元素添加到结果中,并将该元素在哈希表中的出现次数减 1 1 1。

实现方面,为了降低空间复杂度,可以首先遍历较短的数组并使用哈希表记录较短的数组中每个元素的出现次数,然后遍历较长的数组计算交集。

代码

java 复制代码
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        return nums1.length <= nums2.length ? getIntersection(nums1, nums2) : getIntersection(nums2, nums1);
    }

    public int[] getIntersection(int[] shorter, int[] longer) {
        List<Integer> intersectionList = new ArrayList<Integer>();
        Map<Integer, Integer> counts = new HashMap<Integer, Integer>();
        for (int num : shorter) {
            counts.put(num, counts.getOrDefault(num, 0) + 1);
        }
        for (int num : longer) {
            if (counts.containsKey(num)) {
                intersectionList.add(num);
                counts.put(num, counts.getOrDefault(num, 0) - 1);
                if (counts.get(num) == 0) {
                    counts.remove(num);
                }
            }
        }
        int size = intersectionList.size();
        int[] intersectionArr = new int[size];
        for (int i = 0; i < size; i++) {
            intersectionArr[i] = intersectionList.get(i);
        }
        return intersectionArr;
    }
}

复杂度分析

  • 时间复杂度: O ( m + n ) O(m + n) O(m+n),其中 m m m 和 n n n 分别是数组 nums 1 \textit{nums}_1 nums1 和 nums 2 \textit{nums}_2 nums2 的长度。需要使用 O ( m + n ) O(m + n) O(m+n) 的时间遍历两个数组计算交集,然后使用 O ( min ⁡ ( m , n ) ) O(\min(m, n)) O(min(m,n)) 的时间将交集元素填入结果数组,因此时间复杂度是 O ( m + n + min ⁡ ( m , n ) ) = O ( m + n ) O(m + n + \min(m, n)) = O(m + n) O(m+n+min(m,n))=O(m+n)。

  • 空间复杂度: O ( min ⁡ ( m , n ) ) O(\min(m, n)) O(min(m,n)),其中 m m m 和 n n n 分别是数组 nums 1 \textit{nums}_1 nums1 和 nums 2 \textit{nums}_2 nums2 的长度。空间复杂度主要取决于哈希表,哈希表存储较短的数组中每个元素的出现次数,需要 O ( min ⁡ ( m , n ) ) O(\min(m, n)) O(min(m,n)) 的空间。

解法二

思路和算法

解法一的空间复杂度较高,可以使用排序和双指针的做法降低空间复杂度。

首先将两个数组升序排序,排序之后的数组满足相等的元素在数组中的相邻位置,因此可以计算每个元素在两个数组中同时出现的次数。

用 index 1 \textit{index}_1 index1 和 index 2 \textit{index}_2 index2 分别表示数组 nums 1 \textit{nums}_1 nums1 和 nums 2 \textit{nums}_2 nums2 的下标,初始时 index 1 = index 2 = 0 \textit{index}_1 = \textit{index}_2 = 0 index1=index2=0。从左到右遍历两个数组,对于两个数组中遍历到的元素,比较元素大小,执行相应操作。

  • 如果 nums 1 [ index 1 ] = nums 2 [ index 2 ] \textit{nums}_1[\textit{index}_1] = \textit{nums}_2[\textit{index}_2] nums1[index1]=nums2[index2],则当前元素在两个数组中都出现,因此在两个数组的交集内,将当前元素添加到结果中,将 index 1 \textit{index}_1 index1 和 index 2 \textit{index}_2 index2 同时向右移动一位。

  • 如果 nums 1 [ index 1 ] < nums 2 [ index 2 ] \textit{nums}_1[\textit{index}_1] < \textit{nums}_2[\textit{index}_2] nums1[index1]<nums2[index2],则 nums 1 [ index 1 ] \textit{nums}_1[\textit{index}_1] nums1[index1] 不在数组 nums 2 \textit{nums}_2 nums2 中出现,因此不在两个数组的交集中,将 index 1 \textit{index}_1 index1 向右移动一位。

  • 如果 nums 1 [ index 1 ] > nums 2 [ index 2 ] \textit{nums}_1[\textit{index}_1] > \textit{nums}_2[\textit{index}_2] nums1[index1]>nums2[index2],则 nums 2 [ index 2 ] \textit{nums}_2[\textit{index}_2] nums2[index2] 不在数组 nums 1 \textit{nums}_1 nums1 中出现,因此不在两个数组的交集中,将 index 2 \textit{index}_2 index2 向右移动一位。

当 index 1 \textit{index}_1 index1 和 index 2 \textit{index}_2 index2 分别在两个数组的下标范围中时,重复上述操作,直到至少有一个下标超出数组的下标范围,此时不存在更多的同时在两个数组中出现的元素,可以得到交集的全部元素。

由于两个数组都按升序排序,当 nums 1 [ index 1 ] ≠ nums 2 [ index 2 ] \textit{nums}_1[\textit{index}_1] \ne \textit{nums}_2[\textit{index}_2] nums1[index1]=nums2[index2] 时都是将较小的元素对应的下标向右移动,因此较小的元素值一定不在另一个数组中出现,否则另一个数组中的下标在遍历到该元素值时不可能继续向右移动。因此上述做法不会遗漏掉交集内的任何元素。

代码

java 复制代码
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        List<Integer> intersectionList = new ArrayList<Integer>();
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int length1 = nums1.length, length2 = nums2.length;
        int index1 = 0, index2 = 0;
        while (index1 < length1 && index2 < length2) {
            if (index1 < length1 && index2 < length2) {
                if (nums1[index1] == nums2[index2]) {
                    intersectionList.add(nums1[index1]);
                    index1++;
                    index2++;
                } else if (nums1[index1] < nums2[index2]) {
                    index1++;
                } else {
                    index2++;
                }
            }
        }
        int size = intersectionList.size();
        int[] intersectionArr = new int[size];
        for (int i = 0; i < size; i++) {
            intersectionArr[i] = intersectionList.get(i);
        }
        return intersectionArr;
    }
}

复杂度分析

  • 时间复杂度: O ( m log ⁡ m + n log ⁡ n ) O(m \log m + n \log n) O(mlogm+nlogn),其中 m m m 和 n n n 分别是数组 nums 1 \textit{nums}_1 nums1 和 nums 2 \textit{nums}_2 nums2 的长度。对两个数组排序分别需要 O ( m log ⁡ m ) O(m \log m) O(mlogm) 和 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间,使用双指针遍历两个数组计算交集需要 O ( m + n ) O(m + n) O(m+n) 的时间,将交集元素填入结果数组需要 O ( min ⁡ ( m , n ) ) O(\min(m, n)) O(min(m,n)) 的时间,因此时间复杂度是 O ( m log ⁡ m + n log ⁡ n + m + n + min ⁡ ( m , n ) ) = O ( m log ⁡ m + n log ⁡ n ) O(m \log m + n \log n + m + n + \min(m, n)) = O(m \log m + n \log n) O(mlogm+nlogn+m+n+min(m,n))=O(mlogm+nlogn)。

  • 空间复杂度: O ( log ⁡ m + log ⁡ n + min ⁡ ( m , n ) ) O(\log m + \log n + \min(m, n)) O(logm+logn+min(m,n)),其中 m m m 和 n n n 分别是数组 nums 1 \textit{nums}_1 nums1 和 nums 2 \textit{nums}_2 nums2 的长度。空间复杂度主要取决于排序的递归调用栈空间以及存储交集元素的临时数组。

进阶问题答案

第一个进阶问题,如果给定的数组已经排好序,则可以利用解法二的双指针得到两个数组的交集,省略排序操作。由于不需要排序,因此时间复杂度是 O ( m + n ) O(m + n) O(m+n),空间复杂度是 O ( min ⁡ ( m , n ) ) O(\min(m, n)) O(min(m,n))。

第二个进阶问题,可以使用解法一,首先遍历 nums 1 \textit{nums}_1 nums1,使用哈希表记录 nums 1 \textit{nums}_1 nums1 中每个元素的出现次数,即较短的数组每个元素的出现次数,降低空间复杂度。

第三个进阶问题,由于不能将 nums 2 \textit{nums}_2 nums2 的所有元素一次性加载到内存中,因此解法二不适用,只能使用解法一,使用哈希表记录 nums 1 \textit{nums}_1 nums1 中每个元素的出现次数,遍历 nums 2 \textit{nums}_2 nums2 时可以每次读取较少的元素。

相关推荐
Irene19913 小时前
JavaScript 中常用排序方法的性能对比和分析
排序
howard20055 小时前
Hive实战任务 - 9.3 实现学生信息排序和统计
hive·排序·汇总·学生信息
伟大的车尔尼3 天前
双指针题目:两个数组的交集
排序·双指针·哈希表
Chen--Xing3 天前
LeetCode 11.盛最多水的容器
c++·python·算法·leetcode·rust·双指针
lightqjx4 天前
【算法】双指针
c++·算法·leetcode·双指针
利刃大大5 天前
【JavaSE】十三、枚举类Enum && Lambda表达式 && 列表排序常见写法
java·开发语言·枚举·lambda·排序
萌>__<新5 天前
力扣打卡每日一题————最小覆盖子串
数据结构·算法·leetcode·滑动窗口·哈希表
R-G-B5 天前
哈希表(hashtable),哈希理论,数组实现哈希结构 (C语言),散列理论 (拉链发、链接发),散列实现哈希结构,c++ 实现哈希
c语言·哈希算法·散列表·哈希表·数组实现哈希结构·散列实现哈希结构·c++ 实现哈希
波波仔865 天前
clickhouse存储和分区
clickhouse·排序·分区