在问 · 算法题解

350. 两个数组的交集 II

简单二分查找排序 + 二分查找力扣原题 ↗

题目描述

输入两个整数数组 nums1 和 nums2,求它们的交集,但要保留重复。

某个数在 nums1 中出现 a 次、在 nums2 中出现 b 次,那么它在结果中就出现 min(a, b) 次。

以数组形式返回结果,顺序不限。

示例 1
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

解释:2 在两个数组中都出现 2 次,所以结果有两个 2。

示例 2
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

解释:4 和 9 在 nums1 中各出现 1 次,所以各保留 1 个。

数据范围

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

进阶:如果两个数组已经有序,能怎样优化?如果 nums1 远比 nums2 小,哪种做法更合适?如果 nums2 存在磁盘上、内存放不下,只能分批读入,又该怎么做?

解题思路:排序 + 二分查找

两个数组都排序。按从小到大的顺序取 nums1 的每个 x,在 nums2 的“还没用过的后半段” nums2[p:] 上二分,找第一个 >= x 的位置 j。若 nums2[j] == x,就配对成功,记录 x,并把 p 移到 j+1,表示这个 nums2[j] 已被用掉。因为 x 单调不减,前面用掉的位置以后也不会再需要。

为什么正确

nums2[:p] 中的数要么已被配对,要么小于当前 x,以后都不会再用到。

复杂度分析

时间 O(m log m + n log n),空间 O(1)(不计返回值)。

易错点

二分的起点必须是 p 而不是 0,否则同一个 nums2[j] 会被重复配对,结果中的次数会多。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        nums1.sort()
        nums2.sort()
        res = []
        p = 0
        for x in nums1:
            j = bisect_left(nums2, x, p)
            if j < len(nums2) and nums2[j] == x:
                res.append(x)
                p = j + 1
        return res

载入编辑器,自己跑一遍 →

其他解法

哈希表计数是 O(m + n) 时间、O(min(m, n)) 空间;排序做法多一个 log,但几乎不占额外空间,输入已有序时更合适。

解法 2:哈希表计数 时间 O(m + n),空间 O(min(m, n))

用哈希表统计较短数组中每个数字出现的次数。遍历另一个数组,如果当前数字的计数大于 0,就加入答案并把计数减 1。

线性时间,空间只和较短的数组有关。适合一个数组很小、另一个很大或只能分批读入的情况。如果数组已经有序,排序做法更省空间。

class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        if len(nums1) > len(nums2):
            nums1, nums2 = nums2, nums1
        cnt = collections.Counter(nums1)
        res = []
        for x in nums2:
            if cnt[x] > 0:
                res.append(x)
                cnt[x] -= 1
        return res

二分查找 专题的其他题目

查看 二分查找 全部题目 →