LeetCode 第178题:分数排名
题目描述
表: Scores
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| score | decimal |
+-------------+---------+
id 是该表的主键。
该表的每一行都包含了一场比赛的分数。score 是一个有 2 位小数的浮点值。
编写 SQL 查询对分数进行排序。排名按以下规则计算:
- 分数应按从高到低排列。
- 如果两个分数相同,那么两个分数的排名应该相同。
- 在排名相同的分数后,排名数应该是下一个连续的整数。换句话说,排名之间不应该有空缺的数字。
按 score
降序返回结果表。
难度
中等
题目链接
示例
示例 1:
输入:
Scores 表:
+----+-------+
| id | score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
输出:
+-------+------+
| score | rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
提示
- score 的数值取值在 0 到 100 之间。
- 每个 score 都是两位精度的浮点数。
解题思路
方法一:使用窗口函数 DENSE_RANK()
MySQL 支持窗口函数,我们可以使用 DENSE_RANK() 函数对分数进行排名。DENSE_RANK() 函数会为相同的值分配相同的排名,并且排名是连续的,没有空缺。
关键点:
- 使用 DENSE_RANK() 窗口函数按照分数降序排列
- 返回分数和对应的排名
- 最后对结果按照分数降序排序
时间复杂度:O(n log n),其中 n 是 Scores 表中的行数(排序的时间复杂度)
空间复杂度:O(n)
方法二:使用子查询和计数(不使用窗口函数)
对于不支持窗口函数的 MySQL 版本,我们可以使用子查询来实现相同的功能。
关键点:
- 对于每个分数,统计有多少个不同的分数大于或等于它
- 使用子查询和 COUNT(DISTINCT) 实现这个统计
- 最后按照分数降序排序结果
时间复杂度:O(n²),其中 n 是 Scores 表中的行数(嵌套循环的时间复杂度)
空间复杂度:O(n)
方法三:使用用户变量(不使用窗口函数)
这种方法使用 MySQL 的用户变量来跟踪当前排名和分数,是一种更高效的不使用窗口函数的解决方案。
关键点:
- 按照分数降序排序
- 使用用户变量跟踪上一个分数和当前排名
- 当分数变化时更新排名
时间复杂度:O(n log n),其中 n 是 Scores 表中的行数(排序的时间复杂度)
空间复杂度:O(n)
代码实现
SQL 实现(方法一:使用窗口函数)
SELECT score, DENSE_RANK() OVER (ORDER BY score DESC) AS 'rank'
FROM Scores
ORDER BY score DESC;
SQL 实现(方法二:使用子查询)
SELECT S1.score,
(SELECT COUNT(DISTINCT S2.score)
FROM Scores S2
WHERE S2.score >= S1.score) AS 'rank'
FROM Scores S1
ORDER BY S1.score DESC;
SQL 实现(方法三:使用用户变量)
SELECT score,
@rank := IF(@prev = score, @rank, @rank + 1) AS 'rank',
@prev := score
FROM Scores,
(SELECT @rank := 0, @prev := -1) AS init
ORDER BY score DESC;
性能分析
各SQL实现的性能对比:
实现方法 | 执行用时 | 内存消耗 | 特点 |
---|---|---|---|
方法一 | 217 ms | 0B | 使用窗口函数,代码最简洁 |
方法二 | 331 ms | 0B | 使用子查询,适用性广但性能较差 |
方法三 | 238 ms | 0B | 使用用户变量,兼顾性能和兼容性 |
补充说明
代码亮点
- 方法一使用 DENSE_RANK() 窗口函数,代码简洁明了,是最推荐的解决方案
- 方法二不依赖窗口函数,适用于旧版本的 MySQL,但性能较差
- 方法三使用 MySQL 的用户变量,在不支持窗口函数的环境中是一个好的折衷方案
窗口函数说明
- DENSE_RANK():为每一组值分配一个排名,排名是连续的(没有间隔)。相同的值具有相同的排名。
- RANK():与 DENSE_RANK() 类似,但会在相同排名后产生间隔。
- ROW_NUMBER():为每一行分配一个唯一的整数(即使值相同)。
在这道题中,我们需要使用 DENSE_RANK() 而不是 RANK(),因为题目要求"排名之间不应该有空缺的数字"。
常见错误
- 使用 RANK() 而不是 DENSE_RANK(),导致排名不连续
- 在方法三中没有正确初始化用户变量
- 忘记按照分数降序排序最终结果
- 在方法二中没有使用 DISTINCT,导致相同分数被多次计数