LeetCode 602 这道题在 SQL 题单里存在感很强,几乎每个刷过数据库专项的人都会遇到它。题目本身不复杂,就是一个统计“好友数量最大值”的需求,但很多人在第一次提交时都会挂,原因几乎都出在同一个点上:好友关系是双向的,而表里只存了申请人和接受人两个字段。这道题做不对,很多时候不是不会写 GROUP BY,而是没把“谁是谁的好友”这件事想透。这篇文章我想把这道题从读题到多种解法、从踩坑到避坑完整拆一遍,给正准备刷 SQL 题的朋友,也给想在面试前把 UNION、窗口函数、分组聚合这些知识点串一遍的人,提供一个可以直接抄作业的参考。
1. 题目全解析:需求、表结构与“双向好友”这个关键坑
1.1 原题到底问了什么
LeetCode 602 的原始需求很简洁:给定一个好友申请接受记录表 RequestAccepted,里面每一行表示一个用户向另一个用户发送了好友申请,并且对方接受了。要求你找出拥有最多好友的用户,以及他拥有的好友数量。
表结构是三列:
requester_id:申请人 IDaccepter_id:接受人 IDaccept_date:接受日期
(requester_id, accepter_id) 是这张表的主键,意味着同一个申请人向同一个接受人发起的申请只可能出现一次。这个主键约束在后面讨论去重问题时非常关键。
输出结果需要包含两列:id(用户 ID)和 num(好友数量)。题目最初版本要求如果存在并列最多的情况,返回所有并列用户;不过 LeetCode 在评测时一般只校验最终结果集,所以很多提交用 LIMIT 1 也能过,因为测试数据可能没有构造并列场景。但作为刷题和学习,写一个能正确处理并列的解法才是有价值的。
1.2 最容易踩的坑:好友关系是双向的
这是整道题的核心,也是大多数人第一次提交失败的原因。
举个例子:用户 1 向用户 2 发送好友申请,用户 2 接受。这时候表里会有一行 (requester_id=1, accepter_id=2)。注意一个事实:一旦这行记录存在,用户 1 的好友里有了 2,用户 2 的好友里也有了 1。好友关系是双向的,而不是单向的“关注”关系。
如果只统计 requester_id,就会漏掉那些只作为接受方出现、从没主动发过申请的用户。比如原题示例数据里的用户 4,他有一条 accepter_id=4 的记录,但没有任何 requester_id=4 的记录。如果只按 requester_id 分组统计,用户 4 的好友数会被算成 0,这显然是错的。
所以正确的思路是:把每一行记录拆成两个方向,分别代表“这条好友关系中,用户 X 作为申请人这边有一个好友”和“用户 Y 作为接受人这边有一个好友”。先把两个方向的数据合并到一起,再按用户 ID 分组计数,才能得到所有人的好友数量。
1.3 为什么网上的题解版本千差万别
如果你搜索过这道题的题解,会发现写法五花八门:有的用 UNION ALL,有的用 UNION,有的在外面套了 DISTINCT,有的直接 ORDER BY num DESC LIMIT 1,还有的用了窗口函数。这些差异一方面来自 MySQL 版本不同,另一方面来自对题目理解角度不同。
我最开始看题解时也被绕晕过,后来才理解:这些写法在“原题测试数据”下都可能通过,但严谨程度不一样。如果你只是为了 AC,怎么写都行;如果你想通过这道题真正掌握这类“无向关系”统计的套路,建议理解最稳妥的写法,并且知道每种写法在什么场景下会出问题。下面我把整个思路拆成几步来讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解法演进:从直观到优雅的三步拆解
2.1 第一步:UNION ALL 把两个方向拉平
既然好友关系是双向的,那最直接的办法就是把“申请方”和“接受方”两个字段拆开,变成两列用户 ID。这一步在 SQL 里通常用 UNION ALL 来实现。
sql复制SELECT requester_id AS id FROM RequestAccepted
UNION ALL
SELECT accepter_id AS id FROM RequestAccepted;
这个子查询的结果集里,每一行都是一个“用户 ID”,表示“这个用户在这条好友关系中占据了一个端点”。因为有两条 SELECT,所以原始表里每一行会贡献两个用户 ID:一个是申请人,一个是接受人。
这里我建议用 UNION ALL 而不是 UNION,原因后面会详细说。简单来讲,UNION 会做去重,如果我们只是想把所有“端点”收集起来,暂时不需要去重;而且 UNION ALL 不会触发排序去重,性能也更好。
这一步是整个解法的地基。把这个子查询的结果先跑出来看一眼,你就能直观理解“拉平”是什么意思:原本每一行是两个用户之间的关系,现在变成了一个用户 ID 列表,每一行代表一个用户出现在某条好友关系中。
2.2 第二步:GROUP BY 统计每个人的好友总数
有了上面的用户 ID 列表,接下来的事情就顺理成章了:按 ID 分组,统计每个 ID 出现了多少次。
sql复制SELECT id, COUNT(*) AS num
FROM (
SELECT requester_id AS id FROM RequestAccepted
UNION ALL
SELECT accepter_id AS id FROM RequestAccepted
) t
GROUP BY id;
这个结果里,num 就是每个用户的好友数量。为什么 COUNT(*) 就是好友数?因为每一行代表“
