双指针题目:两个数组的交集 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 ≤ nums1i, nums2i ≤ 1000 \texttt{0} \le \texttt{nums1i, nums2i} \le \texttt{1000} 0≤nums1i, nums2i≤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 nums1index1=nums2index2,则当前元素在两个数组中都出现,因此在两个数组的交集内,将当前元素添加到结果中,将 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 nums1index1<nums2index2,则 nums 1 index 1 \textit{nums}_1\\textit{index}_1 nums1index1 不在数组 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 nums1index1>nums2index2,则 nums 2 index 2 \textit{nums}_2\\textit{index}_2 nums2index2 不在数组 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 nums1index1=nums2index2 时都是将较小的元素对应的下标向右移动,因此较小的元素值一定不在另一个数组中出现,否则另一个数组中的下标在遍历到该元素值时不可能继续向右移动。因此上述做法不会遗漏掉交集内的任何元素。

代码

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 时可以每次读取较少的元素。

相关推荐
aqiu1111111 天前
【算法刷题】蓝桥杯:怎么倒茶(贪心算法 + 结构体排序)
贪心·排序
OuO-21 天前
笔试强训 Day 43:kotori 和抽卡(二)、ruby 和薯条、循环汉诺塔
数学·前缀和·二分查找·动态规划·排序·滑动窗口·循环汉诺塔
琥珀色糖2 天前
leetcode hot100题(持续更新)移动零(双指针)
算法·leetcode·职场和发展·双指针·移动零
wabs6664 天前
关于哈希表【力扣15.三数之和的思考】
数据结构·算法·leetcode·散列表·哈希表·三数之和
旖旎夜光5 天前
LeetCode 202:快乐数(双指针问题) —— 题解
数据结构·c++·算法·leetcode·双指针
Tsuki_tl5 天前
【面试高频】 HashMap, HashTable,ConcurrentHashMap 之间的区别
面试·hashmap·synchronized·哈希表·多线程并发·hashtable·分段锁
Tisfy6 天前
LeetCode 3731.找出缺失的元素:哈希 / 排序
算法·leetcode·哈希算法·排序·哈希表
旖旎夜光6 天前
LeetCode 11:盛最多水的容器(双指针问题) —— 题解
数据结构·c++·算法·leetcode·双指针
Forever Nore6 天前
LeetCode 2 两数相加 - 链表
leetcode·链表·哈希表
ShineWinsu6 天前
对于C++中unordered_map的详细介绍
数据结构·c++·算法·面试·stl·哈希表·unordered_map