1341. 电影评分

难度:中等 | 题号:1341 | 链接:1341. 电影评分 - 力扣(LeetCode) | 数据结构:SQL

关联:订单最多的用户(同为「分组计数取 Top-1」)· 分数排名(同为「排名 / 并列处理」)· 部门工资最高的员工(分组最值)· 可对照 SQL原理_02(多表连接 / 排序 / UNION ALL)。

🎯 问题描述

描述:写一个解决方案,同时完成两件事:

  1. 找出评论电影数量最多的用户名。若并列,返回字典序较小的用户名。
  2. 找出 2020 年 2 月里平均评分最高的电影名称。若并列,返回字典序较小的电影名称。

返回结果表的列名为 results。

表:Movies / 表:Users / 表:MovieRating

1
2
3
4
5
6
7
8
9
10
+---------------+---------+      +---------------+---------+      +---------------+---------+
| Column Name | Type | | Column Name | Type | | Column Name | Type |
+---------------+---------+ +---------------+---------+ +---------------+---------+
| movie_id | int | | user_id | int | | movie_id | int |
| title | varchar | | name | varchar | | user_id | int |
+---------------+---------+ +---------------+---------+ | rating | int |
movie_id 是主键。 user_id 是主键。 | created_at | date |
title 唯一。 name 唯一。 +---------------+---------+
(movie_id, user_id) 是联合主键。
created_at 为点评日期。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
输入:
Movies 表: Users 表: MovieRating 表:
+----------+----------+ +--------+-------+ +----------+---------+--------+------------+
| movie_id | title | | user_id| name | | movie_id | user_id | rating | created_at |
+----------+----------+ +--------+-------+ +----------+---------+--------+------------+
| 1 | Avengers | | 1 | Daniel| | 1 | 1 | 3 | 2020-01-12 |
| 2 | Frozen 2 | | 2 | Monica| | 1 | 2 | 4 | 2020-02-11 |
| 3 | Joker | | 3 | Maria | | 1 | 3 | 2 | 2020-02-12 |
+----------+----------+ | 4 | James | | 1 | 4 | 1 | 2020-01-01 |
+--------+-------+ | 2 | 1 | 5 | 2020-02-17 |
| 2 | 2 | 2 | 2020-02-01 |
| 2 | 3 | 2 | 2020-03-01 |
| 3 | 1 | 3 | 2020-02-22 |
| 3 | 2 | 4 | 2020-02-25 |
+----------+---------+--------+------------+
输出:
+----------+
| results |
+----------+
| Daniel |
| Frozen 2 |
+----------+
解释:
- Daniel 和 Monica 都点评了 3 部电影,但 Daniel 字典序较小 → 取 Daniel。
- Frozen 2 与 Joker 在 2 月的平均评分都是 3.5,但 Frozen 2 字典序较小 → 取 Frozen 2。

提示:

  • 两问结果是两条独立的行,需要把两个查询的结果竖着拼起来 → 用 UNION ALL(不要用 UNION,语义上不清、还可能误去重)。
  • 「并列取字典序较小」= ORDER BY 主指标 DESC, 名字 ASC 后 LIMIT 1,一次排序同时解决并列。
  • 第二问要先 WHERE created_at 限定在 2020-02-01 ~ 2020-02-29,再按电影分组取平均分。
  • 每问都要 LIMIT 1 —— 子查询里各自排序取一行,外层再 UNION ALL。

💻 解题思路

思路1:两个子查询各取 Top-1,再 UNION ALL(用户解法)

核心逻辑

把题目拆成两个互不相干的「Top-1」:① 用户名按「评论电影数」降序、名字升序 → 第一行;② 电影名按「2 月平均评分」降序、名字升序 → 第一行。两者列名都对齐成 results,用 UNION ALL 竖拼。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Write your MySQL query statement below
(
SELECT u.name AS results
FROM Users u
JOIN MovieRating mr ON u.user_id = mr.user_id
GROUP BY u.user_id
ORDER BY COUNT(DISTINCT mr.movie_id) DESC, u.name ASC
LIMIT 1
)
UNION ALL
(
SELECT m.title AS results
FROM Movies m
JOIN MovieRating mr ON m.movie_id = mr.movie_id
WHERE mr.created_at >= '2020-02-01'
AND mr.created_at < '2020-03-01'
GROUP BY m.movie_id
ORDER BY AVG(mr.rating) DESC, m.title ASC
LIMIT 1
);

📊 性能分析

  • 时间复杂度:O(nlog⁡n)O(n \log n)(两次分组聚合 + 两次排序;排序是主导项)。
  • 空间复杂度:O(n)O(n)(分组哈希表 / 排序临时表)。

思考

  • COUNT(DISTINCT movie_id) 的必要性:(movie_id, user_id) 是联合主键,理论上一个用户对同一部电影只有一条记录,COUNT(*) 也可行;但写 COUNT(DISTINCT movie_id) 语义更清晰(「评论了多少部电影」而非「多少条记录」),防御性更强。
  • ORDER BY 主指标 DESC, 名字 ASC 是处理「并列取字典序最小」的通用技巧——把并列打平逻辑交给排序,而不是写在 WHERE 里。
  • 日期边界用左闭右开:>= '2020-02-01' AND < '2020-03-01' 比 BETWEEN ... AND '2020-02-29' 更稳(闰年 / 不同月天数都不会错)。
  • 括号包裹子查询 + LIMIT:MySQL 里 UNION ALL 两侧的 ORDER BY ... LIMIT 必须用括号包起来,否则会被当成整体排序;若需要最终结果保持两个子查询的顺序,可在外层再套一层排序或依赖 MySQL 实现(题解一般允许)。
  • UNION vs UNION ALL:本题两行理论上可能同名(某用户名恰好等于某电影名),用 UNION 会被误去重 → 必须 UNION ALL。

思路2:用窗口函数替代 LIMIT(可扩展写法)

核心逻辑

如果题目改成「两个指标都要输出多个并列结果」或需要在同一查询里做更多排名,可把 ORDER BY ... LIMIT 1 换成 RANK() OVER (ORDER BY 指标 DESC, 名字 ASC) 再筛 rk = 1。本题只需一行,LIMIT 1 已足够,此处仅作对 分数排名 一类题的写法呼应。

代码实现

1
2
3
4
5
6
7
SELECT name AS results
FROM (
SELECT u.name, RANK() OVER (ORDER BY COUNT(DISTINCT mr.movie_id) DESC, u.name ASC) AS rk
FROM Users u JOIN MovieRating mr ON u.user_id = mr.user_id
GROUP BY u.user_id
) t
WHERE rk = 1;

思考

  • 用了 RANK 而非 DENSE_RANK:题目要求「并列取一个」,靠名字二次排序已保证唯一,其实 ROW_NUMBER() 也够;RANK 语义上更贴近「第一名」。
  • 两套思路本质相同——先算指标,再排序取第一名;区别只是「筛选第一名」用 LIMIT 还是 rk = 1。

🔗 相关链接

  • 所属 MOC:学习-数据结构与算法MOC
  • 题集索引:LeetCode题集索引
  • 关联概念:SQL原理_02(多表连接 / 排序 / UNION)· SQL原理_03(窗口函数)
  • 同类题目:订单最多的用户 · 分数排名 · 部门工资最高的员工

📝 总结

「多个独立 Top-1 拼装」模板:

  • 每问单独写「分组 → 排序(主指标 DESC, 次要键用于打平)→ LIMIT 1」,用括号包住 ORDER BY ... LIMIT,再用 UNION ALL 竖拼。
  • 三个关键点:① 「并列取字典序最小」交给她 ORDER BY 主指标 DESC, 名字 ASC;② 日期边界用左闭右开;③ 竖拼两个同类结果用 UNION ALL(可能同名,别用 UNION)。

🔗 题集索引:LeetCode题集索引 · 算法地图:学习-数据结构与算法MOC
📎 同类题:订单最多的用户 · 分数排名 · 部门工资最高的员工 | 概念:SQL原理_02 · SQL原理_03