Article / 文章
LeetCode 第184题:部门工资最高的员工
表: Employee +--------------+---------+ | 列名 | 类型 | +--------------+---------+ | id | int | | name | varchar | | salary | int | | departmentId | int | +--------------+---------+ id是
题目描述
表: Employee
+--------------+---------+
| 列名 | 类型 |
+--------------+---------+
| id | int |
| name | varchar |
| salary | int |
| departmentId | int |
+--------------+---------+
id是此表的主键列。
departmentId是Department表中ID的外键。
该表的每一行都表示员工的ID、姓名和工资。它还包含他们所在部门的ID。
表: Department
+-------------+---------+
| 列名 | 类型 |
+-------------+---------+
| id | int |
| name | varchar |
+-------------+---------+
id是此表的主键列。
该表的每一行表示部门ID和部门名称。
编写SQL查询以查找每个部门中薪资最高的员工。 按 任意顺序 返回结果表。
难度
中等
题目链接
示例
示例 1:
输入:
Employee 表:
+----+-------+--------+--------------+
| id | name | salary | departmentId |
+----+-------+--------+--------------+
| 1 | Joe | 70000 | 1 |
| 2 | Jim | 90000 | 1 |
| 3 | Henry | 80000 | 2 |
| 4 | Sam | 60000 | 2 |
| 5 | Max | 90000 | 1 |
+----+-------+--------+--------------+
Department 表:
+----+-------+
| id | name |
+----+-------+
| 1 | IT |
| 2 | Sales |
+----+-------+
输出:
+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT | Jim | 90000 |
| IT | Max | 90000 |
| Sales | Henry | 80000 |
+------------+----------+--------+
解释: Max 和 Jim 在 IT 部门的工资都是最高的,Henry 在销售部的工资最高。
提示
Employee表中的 departmentId 是Department表中 id 的外键。- 一个部门可能有多个获得最高工资的员工。
解题思路
方法一:使用 JOIN 和子查询
解决这道题的关键是找出每个部门的最高工资,然后将这个结果与员工表和部门表连接,找出对应的员工信息。
关键点:
- 使用子查询找出每个部门的最高工资
- 将该子查询与员工表和部门表连接
- 筛选出工资等于部门最高工资的员工
时间复杂度:O(n+m),其中 n 和 m 分别是 Employee 表和 Department 表的行数 空间复杂度:O(n)
方法二:使用 JOIN 和窗口函数
另一种解决方法是使用窗口函数,如 MAX(),对每个部门的员工按工资进行排名,然后筛选出排名第一的员工。
关键点:
- 使用窗口函数 MAX() OVER(PARTITION BY departmentId) 计算每个部门的最高工资
- 将员工表与部门表连接
- 筛选出工资等于部门最高工资的员工
时间复杂度:O(n log n),其中 n 是 Employee 表中的行数(排序的时间复杂度) 空间复杂度:O(n)
方法三:使用 JOIN 和 IN
也可以使用 IN 操作符和子查询,先找出每个部门的最高工资,然后找出满足部门和工资条件的员工。
关键点:
- 使用子查询找出每个部门的最高工资,形成 (departmentId, maxSalary) 对
- 使用 IN 操作符找出满足条件的员工
- 将结果与部门表连接获取部门名称
时间复杂度:O(n*m),其中 n 和 m 分别是 Employee 表和 Department 表的行数 空间复杂度:O(n)
代码实现
SQL 实现(方法一:JOIN 和子查询)
SELECT
d.name AS Department,
e.name AS Employee,
e.salary AS Salary
FROM
Employee e
JOIN
Department d ON e.departmentId = d.id
WHERE
(e.departmentId, e.salary) IN (
SELECT
departmentId, MAX(salary)
FROM
Employee
GROUP BY
departmentId
);
SQL 实现(方法二:JOIN 和窗口函数)
SELECT
Department,
Employee,
Salary
FROM (
SELECT
d.name AS Department,
e.name AS Employee,
e.salary AS Salary,
MAX(e.salary) OVER (PARTITION BY e.departmentId) AS max_salary
FROM
Employee e
JOIN
Department d ON e.departmentId = d.id
) t
WHERE
Salary = max_salary;
SQL 实现(方法三:JOIN 和 IN)
SELECT
d.name AS Department,
e.name AS Employee,
e.salary AS Salary
FROM
Employee e
JOIN
Department d ON e.departmentId = d.id
WHERE
e.salary = (
SELECT
MAX(salary)
FROM
Employee
WHERE
departmentId = e.departmentId
);
性能分析
各SQL实现的性能对比:
| 实现方法 | 执行用时 | 内存消耗 | 特点 |
|---|---|---|---|
| 方法一 | 590 ms | 0B | 使用IN和子查询,代码简洁 |
| 方法二 | 517 ms | 0B | 使用窗口函数,性能较好 |
| 方法三 | 646 ms | 0B | 使用相关子查询,直观但性能较差 |
补充说明
代码亮点
- 方法一使用 (departmentId, salary) IN (…) 形式,代码简洁
- 方法二使用窗口函数,避免了多次扫描表
- 方法三使用相关子查询,思路直观易懂
方法比较
不同方法各有优缺点:
JOIN 和子查询:
- 代码简洁,易于理解
- 性能通常不错,特别是当表已经建立了适当的索引
- 支持多列比较,如 (departmentId, salary) IN (…)
窗口函数:
- 避免了多次扫描表,性能通常更好
- 代码结构清晰,易于扩展
- 不是所有数据库都支持窗口函数
相关子查询:
- 思路最直观,类似于自然语言描述
- 在某些情况下性能可能较差,特别是对大型表
- 对于每一行都要执行一次子查询
对于本题,窗口函数方法通常是更好的选择,因为它既清晰又高效。
常见错误
- 忘记处理同一部门有多个最高工资员工的情况
- 没有正确连接 Department 表获取部门名称
- 没有使用 GROUP BY 进行分组,导致结果不正确
- 结果列名格式不符合要求