「LeetCode」350.两个数组的交集 II

264 阅读3分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。

题目描述🌍

给你两个整数数组 nums1nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

示例 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 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

进阶

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

集合法🧩

解题思路

使用集合 List 而非 HashSet,因为 List 可包含重复元素;逐个判断 nums2 中的元素是否存在于 nums1,一旦发现存在,添加该元素到交集数组中,且在 nums1 中删除该元素,遍历完成后即可获得所求交集。

代码

Java

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        List<Integer> numsList = Arrays.stream(nums1).boxed().collect(Collectors.toList());

        int[] intersection = new int[nums1.length];
        int index = 0;

        for (int i = 0; i < nums2.length; i++) {
            // 判断nums1中是否包含nums2元素
            if (numsList.contains(nums2[i])) {
                // 移除nums1该元素
                numsList.remove(new Integer(nums2[i]));
                // 添加nums1与nums2交集元素
                intersection[index++] = nums2[i];
            }
        }
		// 返回交集部分
        return Arrays.copyOfRange(intersection, 0, index);
    }
}

C++

class Solution {
public:
    vector<int> intersect(vector<int> &nums1, vector<int> &nums2) {
        vector<int> intersect;
        for (int num: nums2) {
            auto pos = std::find(nums1.begin(), nums1.end(), num);
            if (pos != nums1.end()) {
                nums1.erase(pos);
                intersect.push_back(num);
            }
        }
        return intersect;
    }
};

m, n 分别为 nums1, nums2 的数组长度,下同

时间复杂度:O(mn)O(mn)

空间复杂度:O(m+n)O(m+n)

哈希计数法🔮

解题思路

因为同一个元素可能在数组中出现两次,所以我们可以借助哈希表来记录元素出现的次数,将长度较短的数组 nums1 转为哈希表(降低空间复杂度),然后遍历较长的数组 nums2,对于每一个元素,若存在于 nums1 中且对应次数 >0,则将该元素添加到交集数组中,并减少哈希表中对应的元素出现的次数

图解来源于 LeetCode 官网

fig1

代码

Java

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        // 有可能减小空间复杂度
        if (nums2.length < nums1.length) {
            return intersect(nums2, nums1);
        }
        
        Map<Integer, Integer> hashMap = new HashMap<>();
        int[] intersection = new int[nums1.length];
        int index = 0;

//        等价于: Arrays.stream(nums1).boxed().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        for (int num : nums1) {
            // getOrDefault: 如果key存在则获取对应的value, 不存在则返回0
            int count = hashMap.getOrDefault(num, 0) + 1;
            hashMap.put(num, count);
        }

        for (int num : nums2) {
            // containsKey 判断: 以防 NullPointer; 也可用getOrDefault
            if (hashMap.containsKey(num) && hashMap.get(num) > 0) {
                intersection[index++] = num;
                hashMap.put(num, hashMap.get(num) - 1);
            }
        }

        return Arrays.copyOfRange(intersection, 0, index);
    }
}

C++

class Solution {
public:
    vector<int> intersect(vector<int> &nums1, vector<int> &nums2) {
        if (nums2.size() < nums1.size())
            return intersect(nums2, nums1);
        unordered_map<int, int> counts;
        vector<int> intersect;

        for (int num: nums1) {
            counts[num]++;
        }
        for (int num: nums2) {
            if (counts.find(num) != counts.end() && counts[num] > 0) {
                intersect.push_back(num);
                counts[num]--;
            }
        }
        return intersect;
    }
};

时间复杂度:O(m+n)O(m+n)

空间复杂度:O(min(m,n))O(min(m,n)),哈希表的大小不会超过任一数组的长度,即不超过较短的那个数组的长度。

排序 & 双指针🥏

解题思路

排序双指针的思路在上一题中已经讲解清楚了:「LeetCode」349.两个数组的交集

而二者的区别在于元素是否可以重复,所以一旦找到相同元素后,两指针同时后移一位即可(无需像上一题跳过重复元素)。

代码

Java

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);

        int length1 = nums1.length;
        int length2 = nums2.length;
        // 交集元素个数少于任一个数组的长度
        int[] intersection = new int[length1];
        int index1 = 0, index2 = 0, index = 0;

        while (index1 < length1 && index2 < length2) {
            if (nums1[index1] == nums2[index2]) {
                // 将交集元素加入到新数组中, 所有指针同时向后移
                intersection[index++] = nums1[index1];
                index1++;
                index2++;
            } else if (nums1[index1] < nums2[index2]) {
                index1++;
            } else {
                index2++;
            }
        }
        // 只返回含交集元素的部分
        return Arrays.copyOfRange(intersection, 0, index);
    }
}

C++

class Solution {
public:
    vector<int> intersect(vector<int> &nums1, vector<int> &nums2) {
        sort(nums1.begin(), nums1.end());
        sort(nums2.begin(), nums2.end());
        int len1 = nums1.size(), len2 = nums2.size();
        int i = 0, i1 = 0, i2 = 0;
        vector<int> intersection;
        while (i1 < len1 && i2 < len2) {
            if (nums1[i1] < nums2[i2]) {
                i1++;
            } else if (nums1[i1] > nums2[i2]) {
                i2++;
            } else {
                intersection.push_back(nums1[i1]);
                ++i1;
                ++i;
                ++i2;
            }
        }
        return intersection;
    }
};

时间复杂度:O(mlogm+nlogn)O(m·logm + n·logn),两数组的排序时间O(mlogm+nlogn)O(m·logm + n·logn)双指针交错寻找交集元素所耗费时间为 O(m+n)O(m+n)

空间复杂度:O(logm+logn)O(logm + logn)

进阶答疑👨‍💻

第一问

Q:如果给定的数组已经排好序呢?你将如何优化你的算法?

A:如果数组已排序,那就直接用双指针法(方法二),这下时间复杂度降到了 O(m+n)O(m+n),空间复杂度降为 O(1)O(1)

第二问

Q:如果 nums1 的大小比 nums2 小,哪种方法更优?

A:哈希计数法

第三问

Q:如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

A:因为哈希计数法中开头代码做了点处理: nums1 计数,用 nums2 查询,所以如果 nums2 存储在磁盘上,大可放心使用哈希计数法,因为 nums2 仅仅涉及查询,每次只需取一部分处理即可。而排序双指针法因为内存有限,无法对数组进行高效排序。

最后🌅

该篇文章为 「LeetCode」 系列的 No.22 篇,在这个系列文章中:

  • 尽量给出多种解题思路
  • 提供题解的多语言代码实现
  • 记录该题涉及的知识点

👨‍💻争取做到 LeetCode 每日 1 题,所有的题解/代码均收录于 「LeetCode」 仓库,欢迎随时加入我们的刷题小分队!