LeetCode 350简单二分查找
两个数组的交集 II 图解题解
这道题到底在问什么
给定数组 nums1 和 nums2,返回两个数组的交集。结果中每个元素出现的次数,应与它在两数组中都出现的次数(取较小值)一致,结果顺序不限。
- 输入
- nums1=[4,9,5,9], nums2=[9,4,9,8,4,5]
- 输出
- [9,4,9,5]
最优解:一步一步想明白
- 3记住口诀:nums1 建计数表,nums2 来配对,配一个扣一个。下面每帧都在套它。
- 4先把 nums1 做成计数表。现在读到 nums1[0] = 4,下一帧把它的计数加 1。
- 54 的计数更新成 1。计数表记的是「这个数在 nums1 里还剩几次能配」。
- 6先把 nums1 做成计数表。现在读到 nums1[1] = 9,下一帧把它的计数加 1。
- 79 的计数更新成 1。计数表记的是「这个数在 nums1 里还剩几次能配」。
- 8先把 nums1 做成计数表。现在读到 nums1[2] = 5,下一帧把它的计数加 1。
- 95 的计数更新成 1。计数表记的是「这个数在 nums1 里还剩几次能配」。
- 10先把 nums1 做成计数表。现在读到 nums1[3] = 9,下一帧把它的计数加 1。
- 119 的计数更新成 2。计数表记的是「这个数在 nums1 里还剩几次能配」。
- 12nums1 的计数表全部建好了。接下来扫 nums2,每个数来表里「认领」名额。
- 13扫到 nums2[0] = 9,去计数表里查它还剩几个名额:cnt[9] = 2。
- 14cnt[9] 原来大于 0,说明 nums1 那边还留着一个 9,配上!收进答案,并把 cnt[9] 扣成 1。
- 15扫到 nums2[1] = 4,去计数表里查它还剩几个名额:cnt[4] = 1。
- 16cnt[4] 原来大于 0,说明 nums1 那边还留着一个 4,配上!收进答案,并把 cnt[4] 扣成 0。
- 17扫到 nums2[2] = 9,去计数表里查它还剩几个名额:cnt[9] = 1。
- 18cnt[9] 原来大于 0,说明 nums1 那边还留着一个 9,配上!收进答案,并把 cnt[9] 扣成 0。
- 19扫到 nums2[3] = 8,去计数表里查它还剩几个名额:cnt[8] = 0。
- 20cnt[8] 已经是 0,nums1 那边没有多余的 8 了,跳过这个,不进答案。
- 21扫到 nums2[4] = 4,去计数表里查它还剩几个名额:cnt[4] = 0。
- 22cnt[4] 已经是 0,nums1 那边没有多余的 4 了,跳过这个,不进答案。
- 23扫到 nums2[5] = 5,去计数表里查它还剩几个名额:cnt[5] = 1。
- 24cnt[5] 原来大于 0,说明 nums1 那边还留着一个 5,配上!收进答案,并把 cnt[5] 扣成 0。
- 25nums2 全部扫完,答案是 [9,4,9,5]。9 配到 2 个、4 和 5 各 1 个,正好是两边出现次数的较小值。
⚠️ 容易写错的地方
✗ 错:用集合 set 去重再交
✓ 对:用计数表保留重复次数
set 会丢掉「9 出现两次」这种重复,本题要带重复
✗ 错:配对后忘了把计数减 1
✓ 对:收入答案就 cnt[v] 减 1
不减会让同一个名额被反复领取,结果偏多
✗ 错:以为必须挑某个数组才对
✓ 对:本题固定统计 nums1 就对
统计哪个数组都能得正确答案;想更省空间可改统计较短数组,属可选优化
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
cnt = Counter(nums1)
ans = []
for x in nums2:
if cnt[x]:
ans.append(x)
cnt[x] -= 1
return ansC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int, int> cnt;
for (int x : nums1) {
++cnt[x];
}
vector<int> ans;
for (int x : nums2) {
if (cnt[x]-- > 0) {
ans.push_back(x);
}
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
int[] cnt = new int[1001];
for (int x : nums1) {
++cnt[x];
}
List<Integer> ans = new ArrayList<>();
for (int x : nums2) {
if (cnt[x]-- > 0) {
ans.add(x);
}
}
return ans.stream().mapToInt(Integer::intValue).toArray();
}
}复杂度
时间
O(n + m)
建表扫 nums1,配对扫 nums2,各一遍
空间
O(n)
计数表存 nums1 里出现的数
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 两个数组的交集 II 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
如果 nums1 很小、nums2 是磁盘上的超大文件读不进内存怎么办?+
用小的 nums1 建计数表(内存放得下),nums2 以流式逐块读入、边读边查表配对。这样内存只和 nums1 大小相关,是面试常见的「大文件交集」追问。
这题和「交集 I」有什么区别?+
交集 I 不带重复,用两个集合求交即可;交集 II 要保留较小出现次数,必须用计数表逐个配对。识别「带不带重复」决定了用 set 还是 Counter。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 两个数组的交集 II 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。