350. 两个数组的交集 II

难度:简单 | 主题:数组 + 哈希表

题目

给你两个整数数组 nums1 和 nums2,请你以数组形式返回两数组的交集。

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

示例

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

思路

先讲个故事:仓库库存核销

你的仓库里有一份”库存清单”:苹果 3 箱,香蕉 2 箱……

客户下了一张订单,你需要按订单数量消耗库存

  • 订单要 2 箱苹果 → 库存减 2,给客户 2 箱

  • 订单要 5 箱香蕉 → 库存只有 2 箱 → 给客户 2 箱(取较小值)

  • 订单要草莓 → 库存没有 → 跳过

这就是哈希计数法的全部逻辑——库存表 + 消耗计数


引导式推导:从交集 I 到交集 II

349两个数组的交集 只关心”有没有”(唯一性),这道题关心”有几个”(次数)。

哈希计数法(推荐)

把短数组做成”库存表”(值 → 出现次数),遍历长数组:

  1. 查库存 > 0?→ 加入结果,库存减一

  2. 库存 = 0?→ 跳过


graph LR

    A["nums1 (短)<br/>[1,2,2,1]"] --> Counter["Counter<br/>{1:2, 2:2}"]

    B["nums2 (长)<br/>[2,2]"] --> C{"Counter[num] > 0?"}

    Counter --> C

    C -->|是| D["结果 += num<br/>Counter[num] -= 1"]

    C -->|否| E["跳过"]

为什么短的建库存表? 空间复杂度从 O(m + n) 降到 O(min(m, n))。

方法时间空间
哈希计数O(m + n)O(min(m, n))
排序 + 双指针O(m log m + n log n)O(1)

排序 + 双指针法

当数组已排序(或允许排序),双指针空间 O(1):

  • 相等 → 加入结果,两指针同移

  • 不等 → 较小值的指针前移


代码

 
def intersect(self, nums1, nums2):
 
    # 让短数组建计数表,节省空间
 
    if len(nums1) > len(nums2):
 
        return self.intersect(nums2, nums1)    # 交换确保 nums1 是短数组
 
    m = Counter()                              # 初始化计数器
 
    for num in nums1:                          # 遍历短数组,建库存表
 
        m[num] += 1
 
    intersection = []
 
    for num in nums2:                          # 遍历长数组
 
        if (count := m.get(num, 0)) > 0:       # 海象运算符:查库存 > 0
 
            intersection.append(num)           # 消耗一个,加入结果
 
            m[num] -= 1
 
            if m[num] == 0:                    # 库存为 0 时删除键
 
                m.pop(num)
 
    return intersection
 

复杂度

指标解释
时间O(m + n)建表 O(m) + 遍历长数组 O(n)
空间O(min(m, n))短数组的计数表

实战考量

频率分析

出现在:一面简单题,和 349 成对出现。常作为 349 的追问——“如果要求保留次数呢?“

延伸思考

Q:为什么要用短数组建计数表?

A:空间从 O(m + n) 降到 O(min(m, n))。体现空间优化的意识。

Q:如果两个数组都很大,内存放不下 Counter 呢?

A:外部排序后双指针,额外空间 O(1)。或者把大数组分块,逐块读入查小数组的 Counter。

Q:如果数组已排序且要求 O(1) 空间?

A:双指针法,不用排序额外开销。相等时加入结果,同时移动;不相等时移动较小值的指针。

Q:如果要求交集次数取最大值(不是最小值)?

A:取 max(count1, count2) 而不是 min。但题目要求的是最小值。

Q:海象运算符不加括号会怎样?

A:if count := m.get(num, 0) > 0 会解析为 if count := (m.get(num, 0) > 0),count 变成布尔值。所以必须加括号 (count := m.get(num, 0)) > 0

易错点

  • 交换长度后必须 return(递归调用),不能只调不返

  • 海象运算符必须加括号:(count := m.get(num, 0)) > 0

  • 库存为 0 时建议删除键(不是必须,但能减少字典大小)

  • 与 349 的区别:用 Counter 而不是 Set


生活类比

哈希计数 → 库存核销 → 频率交集

就像餐厅盘点食材——你有一张”食材库存表”(番茄 5 个,鸡蛋 3 个),厨师按订单消耗:

  • 番茄炒蛋需要番茄 2 个 → 库存减 2,出菜
  • 如果只剩 1 个番茄 → 取较小值,做少一点

一次遍历建库存,一次遍历消耗。简单来说就是”记好账,一笔笔核销”。


相关题目

题目关系
349两个数组的交集同族基础版(不考虑次数,用集合)
217存在重复元素哈希表的另一应用
350两个数组的交集II本题,计数版交集

→ 返回题单:LeetCode学习路线图 > 一、数组与哈希(含双指针、矩阵)

速记卡(面试闪卡)

Q1:一句话讲清「350. 两个数组的交集 II」到底是什么?

A:统计两数组各自出现次数,取每个元素较小频次,拼出带重复的交集。

Q2:一、题目与频次交集 —— 怎么理解?

A:像仓库按订单核销库存,苹果要 5 箱但只有 2 箱就给 2 箱。交集中每个元素出现的次数取两数组的较小值(Frequency Intersection)。

Q3:二、哈希计数法 —— 怎么理解?

A:像给短数组建一张库存表,遍历长数组时一笔笔消耗计数。用短数组建表省空间,空间从 O(m+n) 降到 O(min)(Hash Counter)。

Q4:三、排序双指针法 —— 怎么理解?

A:像两列队伍并排走,相等的就收下两人同进,不等的小个先迈步。数组已排序时空间 O(1),边走边比(Two Pointers Sorted)。

Q5:四、复杂度与易错点 —— 怎么理解?

A:像记两笔账:建表 O(m) 遍历 O(n),空间只看短数组。易错在海象运算符要加括号、与 349 集合版的区别(Time/Space Complexity)。

Q6:核心速记主线有哪些?

  • 交集保留频次,次数取两数组较小值

  • 哈希计数:短数组建表,长数组消耗

  • 排序双指针可做到 O(1) 额外空间

  • 与 349 区别:用 Counter 而非 Set

口诀

A:交集留频次,

取小不取大;

短表建库存,

一笔笔核销。

相关链接