这面试官是不是有点上头了。
面试了一位某为出来的员工,一道 LeetCode 中等题,归并排序 没写出来,然后直接一句“拜拜吧”。乍一看挺爽,像是技术面试铁面无私,其实细想挺尴尬的。
归并排序会不会,当然能看点基本功。但拿一道题直接判死刑,也挺粗暴。人家在公司里到底干啥,是写业务、搞架构、扛线上、还是天天被需求追着跑,这些完全没看出来。结果就因为现场没憋出代码,整个人被打成“某为员工也不过如此”。
更好笑的是,很多面试到最后都变成背题大赛。平时写代码靠脑子、靠经验、靠排查问题,现场倒计时手写排序,写不出来就像犯了大错。
当然,基础太差肯定不行。但只靠一道题判断一个人行不行,HR看了都得沉默一下。面试不是抽卡鉴定血统啊。
今日面试题
员工薪水这题,第一眼别急着写循环累加。 我见过不少人上来就按月份扫一遍,结果一碰到“只统计最近 3 个月”“还要排除最新月份”,代码马上拧成一团。
题目大概是这个意思:给一批员工月薪记录,每条记录有员工 id、月份 month、薪水 salary。要查每个员工每个月的累计薪水,累计范围是当前月往前最多 3 个月。还有一个坑:每个员工最新的那个月不参与输出。
比如某员工有这些数据:
id=1, month=1, salary=20
id=1, month=2, salary=30
id=1, month=3, salary=40
id=1, month=4, salary=60
那 month=4 是最新月份,不输出。
month=3 的累计薪水就是:
month 1 + month 2 + month 3 = 90
month=2 是:
month 1 + month 2 = 50
month=1 是:
20
这题真正要注意的不是累加,而是顺序。
我一般会先按员工分组,每个员工内部再按月份排序。然后用一个滑动窗口维护最近 3 个月的薪水。窗口里超过 3 条,就把最早那条踢出去。
用 Java 实现起来大概是这样的:
import java.util.*;
public class EmployeeSalaryQuery {
static class SalaryRow {
int id;
int month;
int salary;
SalaryRow(int id, int month, int salary) {
this.id = id;
this.month = month;
this.salary = salary;
}
}
static class SalaryResult {
int id;
int month;
int totalSalary;
SalaryResult(int id, int month, int totalSalary) {
this.id = id;
this.month = month;
this.totalSalary = totalSalary;
}
@Override
public String toString() {
return "id=" + id + ", month=" + month + ", totalSalary=" + totalSalary;
}
}
public static List<SalaryResult> queryCumulativeSalary(List<SalaryRow> rows) {
Map<Integer, List<SalaryRow>> group = new HashMap<>();
for (SalaryRow row : rows) {
group.computeIfAbsent(row.id, k -> new ArrayList<>()).add(row);
}
List<SalaryResult> ans = new ArrayList<>();
for (Map.Entry<Integer, List<SalaryRow>> entry : group.entrySet()) {
List<SalaryRow> list = entry.getValue();
list.sort(Comparator.comparingInt(a -> a.month));
int latestMonth = list.get(list.size() - 1).month;
Deque<SalaryRow> window = new ArrayDeque<>();
int sum = 0;
for (SalaryRow row : list) {
window.addLast(row);
sum += row.salary;
while (window.size() > 3) {
SalaryRow removed = window.removeFirst();
sum -= removed.salary;
}
if (row.month != latestMonth) {
ans.add(new SalaryResult(row.id, row.month, sum));
}
}
}
ans.sort((a, b) -> {
if (a.id != b.id) {
return a.id - b.id;
}
return b.month - a.month;
});
return ans;
}
public static void main(String[] args) {
List<SalaryRow> rows = Arrays.asList(
new SalaryRow(1, 1, 20),
new SalaryRow(1, 2, 30),
new SalaryRow(1, 3, 40),
new SalaryRow(1, 4, 60),
new SalaryRow(2, 1, 50),
new SalaryRow(2, 2, 60)
);
List<SalaryResult> result = queryCumulativeSalary(rows);
for (SalaryResult item : result) {
System.out.println(item);
}
}
}
输出大概是:
id=1, month=3, totalSalary=90
id=1, month=2, totalSalary=50
id=1, month=1, totalSalary=20
id=2, month=1, totalSalary=50
这里有个细节,别忽略。
如果一个员工只有两个月数据,最新月份也要排除,所以只能输出老的那个月。如果只有一个月数据,那这个员工就没有结果。很多人样例能过,边界用例就挂在这里。
这题也可以暴力做:每个员工每个月都往前找 3 个月,然后相加。数据少的时候没问题,但这种写法有点不干净。
滑动窗口更像线上处理日志、账单、月度统计那种写法。数据来了,按时间排好,窗口往前滚,该进的进,该出的出。逻辑短,也不容易把月份条件写乱。
这题最后排序也要看清楚,一般要求按员工 id 升序,员工内部按月份降序。这个地方不影响累计结果,但会影响提交答案。很多错不是算法错,是输出顺序错。