某大厂员工:现在QA开发出来一堆东西,也不好好测试,让研发来测试,AI工具越来越完善,写代码变成了挂机~
刚看到个贴子,说某大厂员工吐槽:现在 QA 开发一堆工具和流程,也不好好测,全丢给研发自己回归;再加上 AI 写代码越来越方便,结果写代码变成“挂机”,验证和背锅成了主业。
我觉得这事吧,本质不是“QA 摸鱼”或者“AI 抢饭碗”,而是分工和责任越来越糊了。
从我的角度看,AI 上线之后,更需要的是:谁负责“对结果负责”。工具再多,没人设计用例、没人思考边界条件,最后翻车一样是全员挨骂。QA 如果只会点点工具不懂业务,确实会被边缘化;研发如果只想写爽代码不愿意测,也早晚会被现实教育。
别指望 AI 和别人替你负责。
面试题:最近的三笔订单
想象你在电商做后端,一个接口是“我的订单”。产品说:列表第一页,只要给我最近的三笔就行,越新越靠前。数据存在库里也好,先查出来放在内存里也好,最后都可以抽象成一个问题:
给定一堆订单,每个订单有下单时间,找到最近的三笔订单,按时间从新到旧返回。
这里我先默认是“某一个用户的一堆订单里找最近三笔”。如果要做“每个用户最近三笔”,只是在这个基础上多一层分组,思路是一样的。
先把模型定下来
先用一个最简单的订单类,里面只保留和题目有关的字段:
publicclassOrder{
privatefinallong id;
privatefinallong createdTime; // 时间戳,越大越新
publicOrder(long id, long createdTime){
this.id = id;
this.createdTime = createdTime;
}
publiclonggetId(){
return id;
}
publiclonggetCreatedTime(){
return createdTime;
}
@Override
public String toString(){
return"Order{id=" + id + ", createdTime=" + createdTime + "}";
}
}
假设你方法的入参就是 List<Order> orders,里面可能有几条,也可能有好几万条。
多数同学第一反应都是:按时间排序,然后取前三个,这个思路完全没问题,而且在订单量不大的时候也很好用。
import java.util.*;
publicclassOrderService{
// 方案一:排序后取前三
public List<Order> latest3BySort(List<Order> orders){
if (orders == null || orders.isEmpty()) {
return Collections.emptyList();
}
// 从新到旧排序
orders.sort((o1, o2) -> Long.compare(o2.getCreatedTime(), o1.getCreatedTime()));
int end = Math.min(3, orders.size());
// 注意要 new 一个新的 List,避免直接 subList 导致外面改影响这里
returnnew ArrayList<>(orders.subList(0, end));
}
}
时间复杂度很好算:排序是 O(n log n),后面取前三是 O(1),整体就当 O(n log n)。
这种写法的优点:
代码短,好读 运行不会太慢,小数据量情况下完全够用
缺点也很明显:其实我只想要三条,却把所有订单都排了一个序,有点浪费。
如果订单量是几十个、几百个,这种浪费不值一提;但如果你是批量离线任务,一次拉几十万条、几百万条订单,就会感觉到排序还是挺费劲的。
换个思路:我只要前三名,没必要全员排序,我只需要在扫描列表的过程中,随时知道“当前为止最大的三个人”。
这种场景特别适合用堆(PriorityQueue),维护一个固定大小为 3 的小顶堆:
堆里最多放 3 个订单
堆顶永远是“当前这三笔里最旧的那一笔”
扫描每个订单:
堆没满,直接丢进去 堆满了,就看新订单是不是比堆顶更新,如果更近,就把堆顶踢出去,放进新订单
扫一遍结束,堆里留下的就是“最近的三笔”,只是顺序还要再调一下。
import java.util.*;
publicclassOrderService{
// 方案二:小顶堆,时间复杂度 O(n log 3) ≈ O(n)
public List<Order> latest3ByHeap(List<Order> orders){
if (orders == null || orders.isEmpty()) {
return Collections.emptyList();
}
// 小顶堆:时间越早(越旧)排在越前
PriorityQueue<Order> heap = new PriorityQueue<>(3,
Comparator.comparingLong(Order::getCreatedTime));
for (Order order : orders) {
if (heap.size() < 3) {
heap.offer(order);
} else {
// 堆顶是当前三条里最旧的订单
Order oldestInTop3 = heap.peek();
if (oldestInTop3 != null &&
order.getCreatedTime() > oldestInTop3.getCreatedTime()) {
heap.poll(); // 踢掉最旧的
heap.offer(order); // 把更新的塞进去
}
}
}
// 这时 heap 里的元素从旧到新,需要翻个顺序
List<Order> result = new ArrayList<>(heap);
result.sort((o1, o2) -> Long.compare(o2.getCreatedTime(), o1.getCreatedTime()));
return result;
}
}
这个方案的特点:
每次操作堆的复杂度是 O(log 3),但log 3是常数,可以当成 1整体就是一遍线性扫描: O(n)特别适合“订单很多,但是只要前 k 条”的场景
如果哪天产品说,不是三笔了,要“最近的一百笔”,这个方案改一下堆的大小就行,整体复杂度还是 O(n log k),对大数据量很友好。
如果你不想引入堆,或者希望少一点对象创建,其实还可以更原始一点:用三个变量,手动维护前三大的时间戳。
思路类似“找数组中最大的三个数”:
publicclassOrderService{
// 方案三:一趟扫描 + 三个指针
public List<Order> latest3Manual(List<Order> orders){
if (orders == null || orders.isEmpty()) {
return Collections.emptyList();
}
Order first = null; // 最新
Order second = null; // 第二新
Order third = null; // 第三新
for (Order order : orders) {
if (first == null ||
order.getCreatedTime() > first.getCreatedTime()) {
// 全新的第一名
third = second;
second = first;
first = order;
} elseif (second == null ||
order.getCreatedTime() > second.getCreatedTime()) {
// 排在第一和第二之间
third = second;
second = order;
} elseif (third == null ||
order.getCreatedTime() > third.getCreatedTime()) {
// 排在第三
third = order;
}
}
List<Order> result = new ArrayList<>();
if (first != null) result.add(first);
if (second != null) result.add(second);
if (third != null) result.add(third);
return result;
}
}
这个方式:
时间复杂度 O(n)额外空间几乎是 O(1)代码读起来略微绕一点,但多看两遍也不难
如果 k 固定很小,比如一直是 3,这种写法是最省的。
上面这些都是“单个用户的一堆订单”。如果是“每个用户最近三笔订单”,常见写法是:
先按用户 id 分组(比如 Map<userId, List>) 每个用户内部用上面任意一个算法
或者更省内存一点:一边流式读取订单,一边用:
Map<Long, PriorityQueue<Order>> map = new HashMap<>();
每个用户维护一个小顶堆,逻辑和上面那个 latest3ByHeap 完全一样,只不过是多套一层 map.get(userId)。
这个思路在实际业务里非常常见,比如按用户取最近几次登录、最近几次付款等等,跟我们平时做高并发订单系统时处理的一些场景非常像
总体上,这道“最近的三笔订单”的题,难度不在语法,而在你能不能把场景拆干净、把不同数据量下合适的方案说清楚: 小数据就排序,大数据用堆,k 很小可以手搓前三,这样面试官一般就会觉得你思路还挺全面的。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html