LeetCode 第177题:第N高的薪水
题目描述
编写一个 SQL 查询,获取 Employee
表中第 n 高的薪水(Salary)。
+-------------+------+
| Column Name | Type |
+-------------+------+
| id | int |
| salary | int |
+-------------+------+
id 是这个表的主键。
表的每一行包含员工的工资信息。
例如,给定上表,如果 n = 2,则应该返回第二高的薪水。如果没有第 n 高的薪水,那么查询应返回 null
。
难度
中等
题目链接
示例
示例1:
输入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+
n = 2
输出:
+------------------------+
| getNthHighestSalary(2) |
+------------------------+
| 200 |
+------------------------+
示例2:
输入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1 | 100 |
+----+--------+
n = 2
输出:
+------------------------+
| getNthHighestSalary(2) |
+------------------------+
| null |
+------------------------+
提示
- 如果没有第 n 高的薪水,查询应返回 null
解题思路
方法一:使用 DENSE_RANK() 窗口函数
MySQL 中可以使用窗口函数 DENSE_RANK() 来为每一行分配一个排名,然后筛选出排名为 n 的行。
关键点:
- 使用 DENSE_RANK() 函数对薪水进行降序排名
- 筛选出排名恰好等于 n 的行
- 使用子查询确保在没有第 n 高薪水时返回 NULL
时间复杂度:O(n log n),其中 n 是 Employee 表中的行数(排序的时间复杂度)
空间复杂度:O(n)
方法二:使用 ORDER BY 和 LIMIT 子句
类似于第二高薪水的解法,我们可以使用 ORDER BY 对薪水进行降序排序,然后使用 LIMIT 和 OFFSET 来获取第 n 高的薪水。
关键点:
- 使用 DISTINCT 去除重复值
- 对薪水进行降序排序
- 使用 LIMIT 1 OFFSET (n-1) 获取第 n 条记录
- 在存储过程中需要特别注意 n-1 的计算
时间复杂度:O(n log n),其中 n 是 Employee 表中的行数(排序的时间复杂度)
空间复杂度:O(n)
代码实现
SQL 实现(方法一:DENSE_RANK() 窗口函数)
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
SELECT
DISTINCT salary
FROM
(SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM Employee) AS ranked
WHERE rnk = N
);
END
SQL 实现(方法二:ORDER BY 和 LIMIT)
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
DECLARE M INT;
SET M = N - 1;
RETURN (
SELECT
DISTINCT salary
FROM
Employee
ORDER BY
salary DESC
LIMIT 1 OFFSET M
);
END
性能分析
各SQL实现的性能对比:
实现方法 | 执行用时 | 内存消耗 | 特点 |
---|---|---|---|
方法一 | 459 ms | 0B | 使用窗口函数,代码简洁 |
方法二 | 387 ms | 0B | 使用OFFSET,性能较好 |
补充说明
代码亮点
- 方法一使用 DENSE_RANK() 窗口函数处理排名问题,语义明确
- 方法二通过变量声明和赋值处理 OFFSET 参数,避免了直接在 LIMIT 语句中进行计算
- 两种方法都使用 DISTINCT 去除重复的薪水值
窗口函数与传统方法的区别
窗口函数的优势:
- 直观性:窗口函数使得排名、计算移动平均等操作更加直观
- 表达力:能够在单个查询中完成复杂的分析任务
- 性能:某些场景下比子查询和自连接更高效
传统方法的优势:
- 兼容性:在不支持窗口函数的数据库中也能使用
- 简单场景:对于简单的查询,传统方法可能更容易理解和维护
常见错误
- 在方法二中忘记声明中间变量 M 并设置为 N-1,导致 OFFSET 参数不正确
- 忘记处理重复薪水的情况
- 没有返回 NULL 当不存在第 n 高的薪水时
- 在 DENSE_RANK() 和 RANK() 之间选择错误,RANK() 会跳过重复的排名