招聘系统模拟
背景介绍
您正在为一家大公司设计一套自动化的招聘系统。系统需要根据多个部门的不同用人标准,从一批应聘者中筛选出最合适的人选。整个招聘过程分为“常规录取”和“补录”两个阶段,每个阶段都有明确的选人规则。您的任务是精确模拟这个过程,并返回最终的录用结果。
核心实体
-
部门 (Department):
-
每个部门有一个唯一的编号(即其在输入数组中的索引)。
-
招聘目标 (
employTarget): 计划招聘的人数。 -
分数门槛:
progmThd: 机考最低分要求。techThd: 技面最低分要求。
-
-
应聘者 (Candidate):
-
每个应聘者有一个唯一的编号(即其在输入数组中的索引)。
-
能力值:
progmGrade: 机考分数。techGrade: 技面分数。
-
招聘流程
整个招聘流程分为两个主要阶段:
第一阶段:常规轮询录取
此阶段的目标是让每个部门都尽量达到其招聘目标 employTarget。
-
轮询机制:
- 系统会进行多轮招聘。
- 在每一轮中,系统会按部门编号从小到大的顺序,让每个尚未招满的部门选择一位应聘者。
- 如果某个部门在本轮中找不到符合要求的应聘者,则本轮该部门轮空。
- 一轮结束后,如果还有部门未招满且还有候选人,则开始下一轮轮询。
-
结束条件: 当所有部门都招满,或者所有剩余的应聘者都不符合任何一个未招满部门的要求时,常规录取阶段结束。
第二阶段:补录
在常规录取结束后,为了吸纳与已录用人才能力相当的候选人,每个部门有一次补录机会。
-
补录顺序: 同样按部门编号从小到大的顺序进行。
-
补录资格:
- 只有那些能力值(即机考和技面分数均完全相同)与该部门在常规录取阶段最后一名录用者完全一样的候选人,才有资格进入该部门的补录环节。
-
补录选择:
- 从所有符合补录资格的候选人中,按照下文的【选人规则】挑选一人进行补录。
- 注意: 一个部门在补录阶段最多只录用一人。即使成功补录了一人,该部门的招聘也彻底结束;如果找不到符合补录资格的人,该部门的招聘也同样结束。
通用【选人规则】
无论是常规录取还是补录,在从一批候选人中挑选最佳人选时,都遵循以下三级优先序规则:
- 门槛要求: 候选人的机考和技面分数必须同时大于或等于部门的分数门槛要求。
- 技面优先: 在满足门槛的候选人中,优先录取技面分数 (
techGrade) 最高的。 - 机考次之: 如果技面分数相同,则录取机考分数 (
progmGrade) 最高的。 - 编号保底: 如果机考和技面分数都相同,则录取应聘者编号最小的。
解答要求
- 时间限制: 1000ms
- 内存限制: 256MB
输入格式
-
第一个参数
deptDemands:- 二维数组,
deptDemands[i]代表部门i的要求[employTarget, progmThd, techThd]。 1 <= deptDemands.length <= 10
- 二维数组,
-
第二个参数
candidates:- 二维数组,
candidates[j]代表应聘者j的分数[progmGrade, techGrade]。 1 <= candidates.length <= 10000
- 二维数组,
输出格式
- 一个列表,其元素也是列表。外层列表的索引对应部门编号,内层列表按录取先后顺序包含该部门录用的应聘者编号。
- 如果某部门没有录取任何人,则对应的内层列表为空
[]。
样例 1 解读
输入:
deptDemands:[[2, 130, 120], [1, 150, 150]]candidates:[[150, 100], [160, 190], [150, 200], [200, 190], [160, 190], [160, 190]]
输出: [[2, 1, 4], [3]]
招聘流程详解:
| 阶段 | 轮次/部门 | 决策分析 | 结果 |
|---|---|---|---|
| 常规录取 | 第 1 轮 | ||
| 部门 0 (目标2) | 在所有未录用者中,满足门槛 [130, 120] 的有 {1, 2, 3, 4, 5}。其中技面分最高(200)的是应聘者 2。 | 部门0 录取 2。已录:[2]。 | |
| 部门 1 (目标1) | 在剩余未录用者中,满足门槛 [150, 150] 的有 {1, 3, 4, 5}。其中技面分最高(190)的有 {1, 3, 4, 5}。在他们之中,机考分最高(200)的是应聘者 3。 | 部门1 录取 3。已录:[3]。部门1招满,退出轮询。 | |
| 第 2 轮 | |||
| 部门 0 (目标2, 还差1) | 在剩余未录用者 {0, 1, 4, 5} 中,满足门槛 [130, 120] 的有 {1, 4, 5}。他们的技面分(190)和机考分(160)都相同。按规则,录取编号最小的应聘者 1。 | 部门0 录取 1。已录:[2, 1]。部门0招满,常规录取结束。 | |
| 补录阶段 | 部门 0 | 部门0常规录取的最后一人是 1 ([160, 190])。在剩余应聘者 {0, 4, 5} 中,能力值相同的有 {4, 5}。在 {4, 5} 中按选人规则,选编号最小的 4。 | 部门0 补录 4。最终录取:[2, 1, 4]。 |
| 部门 1 | 部门1常规录取的最后一人是 3 ([200, 190])。剩余应聘者 {0, 5} 中没有能力值相同的。 | 部门1 补录失败。最终录取:[3]。 |
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* 模拟一个复杂的招聘策略系统.
* 该系统包含常规轮询录取和特殊补录两个阶段,
* 并遵循一个多层次的优先录取规则。
*/
public class RecruitmentSystem {
/**
* 内部类,用于表示一个应聘者.
* 封装了应聘者的所有信息,包括其原始编号(ID)、分数和是否已被录用的状态。
*/
private static class Candidate {
int id; // 原始编号 (即在输入数组中的索引)
int progmGrade; // 机考分数
int techGrade; // 技面分数
boolean isHired; // 状态:是否已被录用
public Candidate(int id, int progmGrade, int techGrade) {
this.id = id;
this.progmGrade = progmGrade;
this.techGrade = techGrade;
this.isHired = false;
}
}
/**
* 内部类,用于表示一个招聘部门.
* 封装了部门的需求、门槛以及已录用的人员列表。
*/
private static class Department {
int id; // 部门编号 (即在输入数组中的索引)
int employTarget; // 招聘目标人数
int progmThd; // 机考最低分要求
int techThd; // 技面最低分要求
List<Candidate> hiredCandidates; // 已录用人员列表 (按录取顺序)
public Department(int id, int employTarget, int progmThd, int techThd) {
this.id = id;
this.employTarget = employTarget;
this.progmThd = progmThd;
this.techThd = techThd;
this.hiredCandidates = new ArrayList<>();
}
}
/**
* 主方法,执行整个招聘流程.
* @param deptDemands 部门需求数组
* @param candidates 应聘者信息数组
* @return 按部门编号升序排列的录取结果列表
*/
public List<List<Integer>> processRecruitment(int[][] deptDemands, int[][] candidates) {
// --- 准备阶段:将输入数组转换为面向对象的模型 ---
// 创建所有应聘者对象
List<Candidate> allCandidates = new ArrayList<>();
for (int i = 0; i < candidates.length; i++) {
allCandidates.add(new Candidate(i, candidates[i][0], candidates[i][1]));
}
// 创建所有部门对象
List<Department> allDepartments = new ArrayList<>();
for (int i = 0; i < deptDemands.length; i++) {
allDepartments.add(new Department(i, deptDemands[i][0], deptDemands[i][1], deptDemands[i][2]));
}
// 定义核心的“选人规则”比较器
// 1. 技面分数降序
// 2. 机考分数降序
// 3. 编号升序
Comparator<Candidate> selectionComparator = (c1, c2) -> {
if (c1.techGrade != c2.techGrade) {
return Integer.compare(c2.techGrade, c1.techGrade);
}
if (c1.progmGrade != c2.progmGrade) {
return Integer.compare(c2.progmGrade, c1.progmGrade);
}
return Integer.compare(c1.id, c2.id);
};
// --- 阶段一:常规录取 ---
// 使用一个标志位来控制轮询是否继续。只要在一轮中有任何一个部门招到了人,就可能需要下一轮。
boolean anyDeptHiredInRound = true;
while (anyDeptHiredInRound) {
anyDeptHiredInRound = false; // 每轮开始前,假设本轮不会有任何录取发生
// 按部门编号从小到大轮询
for (Department dept : allDepartments) {
// 如果部门尚未招满
if (dept.hiredCandidates.size() < dept.employTarget) {
// 1. 筛选出所有符合该部门门槛且尚未被录用的应聘者
List<Candidate> eligibleCandidates = new ArrayList<>();
for (Candidate c : allCandidates) {
if (!c.isHired && c.progmGrade >= dept.progmThd && c.techGrade >= dept.techThd) {
eligibleCandidates.add(c);
}
}
// 2. 如果有符合条件的应聘者
if (!eligibleCandidates.isEmpty()) {
// 3. 按照选人规则排序,找到最优人选
eligibleCandidates.sort(selectionComparator);
Candidate bestCandidate = eligibleCandidates.get(0);
// 4. 录用最优人选
bestCandidate.isHired = true;
dept.hiredCandidates.add(bestCandidate);
// 标记本轮有录取发生,需要继续轮询
anyDeptHiredInRound = true;
}
}
}
} // 常规录取循环结束
// --- 阶段二:补录 ---
// 常规录取结束后,按部门编号顺序进行一次性补录
for (Department dept : allDepartments) {
// 补录的前提是部门在常规阶段至少招到了一人
if (dept.hiredCandidates.isEmpty()) {
continue;
}
// 获取常规录取阶段最后一名被录用的人员
Candidate lastHired = dept.hiredCandidates.get(dept.hiredCandidates.size() - 1);
// 1. 筛选出所有与 "lastHired" 能力完全相同且尚未被录用的应聘者
List<Candidate> supplementaryCandidates = new ArrayList<>();
for (Candidate c : allCandidates) {
if (!c.isHired && c.progmGrade == lastHired.progmGrade && c.techGrade == lastHired.techGrade) {
supplementaryCandidates.add(c);
}
}
// 2. 如果有符合补录条件的应聘者
if (!supplementaryCandidates.isEmpty()) {
// 3. 同样按照选人规则排序,找到最优人选
supplementaryCandidates.sort(selectionComparator);
Candidate bestCandidate = supplementaryCandidates.get(0);
// 4. 补录最优人选(最多一人)
bestCandidate.isHired = true;
dept.hiredCandidates.add(bestCandidate);
}
} // 补录结束
// --- 阶段三:格式化输出结果 ---
List<List<Integer>> result = new ArrayList<>();
// 按部门原始顺序(0, 1, 2...)生成结果
for (Department dept : allDepartments) {
// 将部门录用列表中的 Candidate 对象转换为他们的 ID
List<Integer> hiredIds = dept.hiredCandidates.stream()
.map(c -> c.id)
.collect(Collectors.toList());
result.add(hiredIds);
}
return result;
}
}