LeetCode 182. 查找重复的电子邮箱
题目描述
题意分析
Person(id, email)中id唯一,但同一个NULL,并允许任意输出顺序。输出粒度是“每个邮箱一行”,因此先按
COUNT(*)就是该邮箱的出现次数,再用HAVING COUNT(*) > 1保留重复组。
WHERE在分组前过滤原始行,不能直接引用聚合结果;HAVING在分组后过滤组,这正是两者最核心的区别。样例
a@b.com, c@d.com, a@b.com分成两个组:a@b.com的计数为 2,c@d.com的计数为 1,因此只输出前者。
解法:SQL 分组统计
核心思路
GROUP BY email把所有相同邮箱折叠成一个逻辑组。聚合后的不变量是:结果流中的每一行代表一个唯一邮箱,并携带该邮箱在原表中的完整行数。
HAVING COUNT(*) > 1只保留计数超过 1 的组。最终投影邮箱列即可,不需要额外DISTINCT:分组本身已经保证每个邮箱只出现一行。使用显式列名
GROUP BY email比GROUP BY 1更清楚,也不会因调整SELECT列顺序而改变含义。
解题步骤
- 从
Person读取所有记录。- 按
- 对每组执行
COUNT(*)。- 在
HAVING阶段保留计数大于 1 的组。- 输出邮箱并命名为题目要求的
代码实现
SELECT email AS Email
FROM Person
GROUP BY email
HAVING COUNT(*) > 1;
复杂度分析
设表中有 $N$ 行、不同邮箱有 $U$ 个。实际代价由数据库执行计划决定:
- 哈希聚合平均需要 $O(N)$ 时间和 $O(U)$ 临时空间。
- 排序聚合需要 $O(N \log N)$ 时间;排序可能使用 $O(N)$ 内存,也可能在数据量大时落盘。
- 若存在按
关键点总结
- “找重复值”的通用 SQL 骨架是
GROUP BY key HAVING COUNT(*) > 1。WHERE过滤行,HAVING过滤聚合后的组;聚合条件必须放在后者。GROUP BY已经完成去重,外层再加DISTINCT没有必要。COUNT(*)表示组内行数,最符合题意。这里COUNT(email)也因题目保证非空而等价,但换到可空列时两者会不同。- 面试追问“大小写是否视为同一邮箱”时,应先确认数据库排序规则;若要求显式忽略大小写,可按
LOWER(email)分组并统一输出规范。
易错点总结
- 写成
WHERE COUNT(*) > 1:WHERE执行时分组和计数尚未产生,SQL 会报聚合函数使用位置错误。- 按
id分组:id是主键,每组永远只有一行,COUNT(*) > 1永远不成立。- 只写
SELECT DISTINCT email:它只能去重,无法区分“原来出现一次”和“原来出现多次”,会把所有邮箱都输出。- 使用
HAVING COUNT(*) >= 1:所有非空组都满足,样例中的c@d.com会被错误输出;重复必须严格大于 1。- 用
GROUP BY 1依赖列位置:当前查询可能正确,但加入计数列或调整投影顺序后容易悄悄改变分组目标,面试代码应写清列名。- 默认大小写规则:不同数据库或列排序规则可能让
A@b.com与a@b.com分到同组或不同组;本题已保证小写,扩展场景必须明确归一化规则。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 196. 删除重复的电子邮箱 | 简单 | 不只识别重复,还要按最小 ID 保留一行并删除其余行 |
| 596. 超过 5 名学生的课 | 简单 |
GROUP BY + HAVING,但需按学生去重后计数 |
| 586. 订单最多的客户 | 简单 | 分组计数后再按计数排序取最大值 |
| 619. 只出现一次的最大数字 | 简单 | 用 HAVING COUNT(*) = 1 筛唯一组,再求最大值 |