PTA题目集567电梯迭代
一、前言
这三次电梯题目系统地考察了Java编程中的多项核心技能,主要包括类的设计、集合框架使用(如数组和链表)、数据操作(排序、查找、删除)等关键技术。我认为这三次作业最核心的挑战在于算法逻辑的设计与实现。具体来看,题目集5作为基础篇,主要考察基本数据处理能力和简单调度逻辑的实现。题目集6在此基础上增加了面向对象设计的复杂度,需要处理电梯与乘客的交互关系,特别是要解决不合理请求过滤和重复请求去重的问题。到了题目集7,题目要求更加全面,不仅逻辑复杂度显著提升,还需要考虑更接近实际场景的各种特殊情况。总体而言,这三次题目采用了循序渐进的迭代设计方式,从最基础的电梯调度功能开始,逐步增加功能模块和业务复杂度,最终形成一个相对完整的电梯调度系统。这种由浅入深的设计思路虽然对Java初学者来说颇具挑战性,但确实能有效提升我们的编程能力和系统设计思维。特别值得一提的是重量级电梯题目,它完美体现了"迭代开发"的思想。每次题目都在前一次基础上增加新功能,要求我们不断重构和优化代码结构。这种设计让我深刻理解了软件工程中"增量开发"的重要性。
二、设计与分析
题目集5:
1.核心结构分析
- 分层处理:将电梯系统分为主控模块(Main)和电梯核心模块(Elevator)
- 请求分类:区分内部请求()和外部请求(<n,UP/DOWN>)
- 状态管理:通过direction(IDLE/UP/DOWN)和state(Stopped)跟踪电梯状态
2. 请求处理流程
-
输入解析:
- 使用Scanner读取最小/最大楼层
- 循环读取请求直到"end"指令
- 通过正则表达式区分内外请求
-
请求分类存储:
- 内部请求存入innerElevator
- 外部请求存入outerElevator
-
调度执行:
- 调用performActions()开始处理请求队列
3. 核心算法逻辑
-
多条件调度策略:
if (!innerRequests.isEmpty() && !outerRequests.isEmpty()) { // 处理同时存在的内外请求 } else if (outerRequests.isEmpty()) { // 仅处理内部请求 } else if (innerRequests.isEmpty()) { // 仅处理外部请求 } -
方向决策机制:
- 空闲状态时根据第一个请求决定方向
- 运行中优先处理同方向请求
- 通过sameDirection()判断请求方向一致性
-
移动控制:
elevatorMove(currentFloor, targetFloor, direction); requests.remove(0); // 处理完移除请求
4. 关键设计特点
-
状态驱动:
- 通过direction状态决定处理逻辑
- 三种主要状态:IDLE/UP/DOWN
-
请求优先级:
- 同方向请求优先处理
- 内部请求和同方向外部请求比较距离
-
移动模拟:
- elevatorMove()实现楼层遍历打印
- 包含开门关门动作模拟
-
异常处理:
- 通过extractFloorNumber()安全提取楼层号
- 请求格式校验使用正则表达式
5.时间复杂度分析
-
请求处理主循环:
performActions()的 while 循环:O(N),N 为总请求数- 内部每次调用
handleBothRequests()等处理方法:O(1) 基本操作
-
请求匹配和移除:
ArrayList的remove(0)操作:O(N)(需要移动元素)- 最坏情况下(交替处理内外请求):O(N²)
-
移动模拟:
elevatorMove()的楼层遍历:O(M),M 为楼层跨度
6.空间复杂度分析
-
存储结构:
- 两个
ArrayList存储请求:O(N) - 固定数量的状态变量:O(1)
- 两个
-
临时变量:
- 方法调用栈:O(K),K 为递归深度(当前实现无递归)
- 楼层遍历时的循环变量:O(1)
总体空间复杂度:O(N)
题目集6:
1. 核心结构分析
分层架构:
Passenger:数据模型层,封装乘客的起始和目标楼层RequestQueue:数据管理层,处理请求的存储和检索Elevator:设备控制层,负责电梯移动和状态维护Controller:调度逻辑层,实现核心调度算法
请求分类:
- 内部请求:
<n>格式,直接存入TreeSet自动排序 - 外部请求:
<n,d>格式,存入LinkedList保持顺序
状态管理:
- 使用
Direction枚举(UP/DOWN/IDLE)精确控制方向 - 通过
current字段实时跟踪电梯位置
2. 请求处理流程
输入解析:
// 读取楼层范围
int min = sc.nextInt();
int max = sc.nextInt();
// 持续读取请求
while (true) {
String line = sc.nextLine().trim();
if (line.equalsIgnoreCase("end")) break;
q.add(line); // 请求入队
}
请求存储:
- 正则表达式
<(\\d+)(?:,(\\d+))?>解析请求 - 内部请求存入
TreeSet实现自动排序 - 外部请求存入
LinkedList保持到达顺序
调度执行:
new Controller(elevator, queue).process(); // 启动调度引擎
3. 核心算法逻辑
调度策略:
while (true) {
handleCurrent(); // 处理当前楼层
targets = getTargets(); // 获取同向目标
if (targets.isEmpty()) {
targets = getOppositeTargets(); // 检查反向目标
elevator.reverseDir(); // 必要时转向
}
moveTo(target); // 执行移动
}
方向决策:
- 优先处理当前方向的请求
- 同方向无请求时自动反转
- 通过
NavigableSet.tailSet/headSet高效定位目标
移动控制:
void moveTo(int target) {
Direction newDir = target > current ? UP : DOWN;
if (newDir != dir) updateDirection(newDir);
while (current != target) {
current += (dir == UP) ? 1 : -1;
printStatus(); // 实时输出状态
}
}
4. 关键设计特点
数据结构优化:
TreeSet保证O(logN)的查询效率LinkedList实现O(1)的请求删除NavigableSet提供高效的范围查询
状态管理:
- 枚举确保方向状态的安全性
- 每次移动前检查方向一致性
- 通过
reverse()方法简化方向切换
异常处理:
private boolean valid(int f) {
return f >= minFloor && f <= maxFloor; // 楼层范围校验
}
5. 时间复杂度分析
请求处理:
- 请求添加:O(logN)(TreeSet自动排序)
- 请求查询:O(1)~O(logN)(取决于数据结构)
- 请求移除:O(1)(LinkedList)/O(logN)(TreeSet)
移动过程:
- 楼层遍历:O(M)(M为移动跨度)
- 方向切换:O(1)常量时间
总体复杂度:
- 最优情况:O(NlogN + M)
- 最差情况:O(N²)(极端请求分布)
6. 空间复杂度分析
存储结构:
TreeSet存储内部请求:O(N)LinkedList存储外部请求:O(M)- 电梯状态变量:O(1)
临时消耗:
- 方法调用栈:O(1)(无递归)
- 移动过程变量:O(1)
总体空间:O(N + M)(与请求量线性相关)
题目集7:
1. 核心结构分析
分层架构:
Passenger:乘客数据模型,记录源楼层、目标楼层和电梯内状态Elevator:电梯实体,管理内部请求队列和当前乘客RequestQueue:外部请求队列,存储待处理的乘客请求ElevatorController:调度中枢,协调电梯和请求队列的交互
状态管理:
- 电梯方向状态:
UP/DOWN字符串表示 - 乘客状态:
isInsideElevator标志位 - 楼层范围验证:通过
minFloor/maxFloor约束
2. 请求处理流程
输入处理:
// 读取楼层范围
int minFloor = scanner.nextInt();
int maxFloor = scanner.nextInt();
// 持续解析请求
Pattern.compile("<(\\d+)(?:,(\\d+))?>"); // 正则匹配两种请求格式
请求分发:
- 内部请求(单数字):
elevator.addInternalRequest() - 外部请求(双数字):
requestQueue.addPassenger()
调度循环:
while (!requestQueue.isEmpty() || elevator.hasInternalRequests()) {
// 1. 判断是否开门
// 2. 处理上下客
// 3. 决定移动方向
// 4. 执行移动
}
3. 核心算法逻辑
开门条件判断:
boolean shouldOpenDoor() {
// 条件1:当前楼层有内部请求
// 条件2:当前楼层有同方向外部请求
// 条件3:外部请求目标方向与电梯当前方向匹配
}
方向决策:
void determineDirection() {
if (有内部请求) {
根据最近内部请求调整方向
} else {
扫描外部请求:
- 仅上方有请求:向上
- 仅下方有请求:向下
- 上下都有请求:保持原方向
}
}
乘客流转:
// 上客处理:
if (乘客方向与电梯方向一致) {
elevator.addPassenger(p);
requestQueue.removePassenger(p);
}
// 下客处理:
passengers.removeIf(p -> p到达目标楼层);
4. 关键设计特点
数据结构选择:
- 内部请求:
Queue<Integer>(FIFO处理) - 外部请求:
ArrayList<Passenger>(随机访问) - 当前乘客:
ArrayList<Passenger>(便于批量移除)
状态同步机制:
- 通过
isInsideElevator标志追踪乘客位置 - 方向变更时自动更新内部请求处理顺序
- 每次移动后立即更新楼层显示
异常防护:
// 楼层有效性检查
if (floor >= minFloor && floor <= maxFloor) {...}
// 请求去重
!internalRequests.contains(floor)
5. 时间复杂度分析
关键操作:
-
请求添加:
- 内部请求:O(1)(Queue追加)
- 外部请求:O(1)(ArrayList追加)
-
请求处理:
- 乘客上下车:O(N)(ArrayList遍历)
- 方向决策:O(M)(外部请求扫描)
-
移动过程:
- 每层移动:O(1)基本操作
总体复杂度:
- 最优情况:O(N + M)(线性处理)
- 最差情况:O(N²)(大量同楼层请求)
6. 空间复杂度分析
存储消耗:
Queue<Integer>:O(K)(内部请求数)ArrayList<Passenger>x2:O(N+M)(乘客+外部请求)- 电梯状态:O(1)固定
临时消耗:
- 方法调用栈:O(1)(无递归)
- 临时集合:O(N)(处理时的乘客拷贝)
总体空间:O(N + M + K)(与请求量成正比)
三、踩坑心得
题目集5的惨痛教训
问题1:超时但测试样例通过
我的第一次提交所有测试样例都通过了,但却因为超时被判0分。😭

第一次电梯题目开始时存在一些问题,当时对题目掉以轻心,到最后没有留出足够的时间来解决问题,最后导致可用时间过少,最后也没有修改成功。
题目集6的调试历程
1.初始阶段问题
- 方向逻辑混乱:电梯在空载时频繁改变方向
- 测试用例:
<1,3>+<5,2>组合请求时出现无效往返 - 解决方法:重构
determineDirection(),增加外部请求扫描优先
2.边界条件
- 极端案例:
<MAX,MIN>+<MIN,MAX>同时请求 - 问题:电梯在端点楼层方向判断错误
- 补丁:在
move()方法增加楼层越界保护
3.状态同步
- 偶发BUG:开门后未及时移除内部请求
- 现象:电梯在目标楼层反复开关门
- 修复:在
openDoor()后立即执行removeNextInternalRequest()
可惜最后仍然无法通过测试点,通过修改也无法解决问题,最后超过了截止时间😭
题目集7的突破
关键发现:回溯前两次作业
在题目集7卡壳时,我意识到问题可能源自前两次作业的基础设计。于是我从前两次作业入手,经过修改前两次作业,成功通过了第一次作业,第三次作业时的大部分也顺利通过。
四、改进建议
1. 先把基础功能做扎实
我发现自己经常被复杂的调度算法绕晕,其实应该先保证:
- 电梯能正确响应最基本的上下请求(比如单独处理"<2>"或"<1,4>")
- 移动逻辑要完全正确(UP时+1,DOWN时-1)
- 开关门时机要精确(比如到达请求楼层必须停)
2. 学会用测试用例debug
踩过的坑:
- 以为代码没问题,但PTA就是不给过
- 自己想的测试用例太简单(比如只测连续上楼)
现在我会:
// 专门测试边界情况的例子
"<1>" // 最小楼层请求
"<MAX>" // 最大楼层请求
"<1,MAX>" // 跨全楼请求
"<2,1>" // 反方向请求
"<1>" + "<1>" // 重复请求
3. 代码结构要整洁
之前把全部逻辑堆在main()里,现在学会了:
- 用多个类分工(比如把乘客、电梯、控制器分开)
- 方法不超过20行(长的就拆成小方法)
- 重要逻辑加注释,比如:
// 判断是否开门的三要素:
// 1. 有内部请求 2. 有外部请求 3. 方向匹配
4. 数据结构要选对
血泪教训:用ArrayList频繁删除元素超级慢!
优化方案:
- 内部请求改用
PriorityQueue自动排序 - 乘客列表用
LinkedList快速增删 - 楼层检查用
HashSet快速查询
5. 学会"作弊"技巧
从同学那里学来的实用技巧:
- 先用正则表达式
<(\d+),?(\d+)?>统一处理输入 - 方向判断用字符串比数字方便(比如直接比较"UP"/"DOWN")
- 复杂的if-else换成状态模式(虽然我还没完全学会)
6. 时间管理建议 ⏰
- 不要等到截止前才做(电梯题至少要留3天)
- 每天实现一个小功能(比如今天搞定方向判断,明天处理请求队列)
- 遇到卡壳就休息会儿,回来再看经常能发现明显错误
虽然现在的代码还有很多不足,但每次改进都能学到新东西。相信下次会做得更好!🚀
五、总结
1. 学习收获
这三次电梯题目集让我从“面向过程”逐渐转向“面向对象”编程,收获真的很大!
(1)面向对象思维
- 学会了用类来封装数据和逻辑(比如
Elevator、Passenger、RequestQueue) - 理解了单一职责原则(一个类只做一件事,比如
Elevator只管移动,Controller只管调度) - 尝试了分层设计(数据层、逻辑层、控制层分离)
(2)数据结构与算法
- 发现
ArrayList删除元素很慢,改用LinkedList或PriorityQueue优化 - 学会了用正则表达式(
Pattern和Matcher)解析输入 - 理解了时间复杂度的重要性(比如
O(N²)的嵌套循环会导致超时)
(3)调试与测试
- 学会了用
System.out.println()打印关键变量,跟踪程序执行流程 - 学会了构造边界测试用例(比如最小/最大楼层、重复请求、反向请求)
- 发现PTA的隐藏测试点往往比样例更严格,不能只满足于样例通过
(4)代码规范与可读性
- 学会了写注释(尤其是复杂逻辑,比如方向决策)
- 学会了拆分长方法(比如把
determineDirection()拆成几个小方法) - 发现命名规范很重要(比如
currentFloor比cf清晰得多)
2. 哪些地方需要进一步学习?
虽然进步很大,但还有很多不足:
(1)设计模式
- 目前代码还是“硬编码”逻辑,想学状态模式(
State Pattern)来优化电梯方向管理 - 听说观察者模式(
Observer Pattern)适合处理事件驱动,想试试
(2)算法优化
- 现在的调度算法还是“先到先服务”,想学更智能的LOOK算法或SCAN算法
- 想研究怎么用优先队列(
PriorityQueue)优化请求处理顺序
(3)调试技巧
- 目前主要靠
println,想学会用IDEA的Debugger(断点、单步执行) - 想学单元测试(JUnit),避免手动测试太麻烦
(4)代码复用
- 现在每次作业都重写很多代码,想学会抽象通用逻辑(比如
Request基类) - 想研究接口(
Interface)和继承怎么用更合理
3. 改进建议
(1)课程 & 教学
优点:老师讲解很清晰,尤其是类设计和正则表达式部分
建议:
能不能在讲完理论后,给个完整的小Demo(比如一个简化版电梯代码)?
希望多讲调试技巧(比如怎么用Debugger找BUG)
(2)作业 & 实验
优点:PTA自动评测很方便,能快速知道对错
建议:
希望提前发布作业要求(比如周末发布,下周一交,时间有点紧)
能不能开放部分隐藏测试点?有时候样例过了但隐藏点没过,不知道怎么改




浙公网安备 33010602011771号