上个月空降了个总监,听说200万从某大厂挖来的,他一来要大换血。团队开始站队:一波人疯狂表忠心,一波人消极抵抗,我选择观望。
刚看到个贴子,说公司空降了个年薪两百万的总监,上来就要动大手术。原团队一部分人赶紧表态,恨不得把忠诚写脸上;一部分人直接摆烂,楼主夹在中间先看戏。
结果三个月一盘点:表忠心的确被“提拔”了,但全是又累又脏的活;硬刚的那拨人,话不多说,慢慢被晾一边了。
网友们的回复我看了看,有人说“职场就该会站队”,有人让楼主赶紧跑,还有人骂领导是PUA体质。
怎么说呢,我的看法是:站不站队是其次,先想清楚自己能提供什么价值。你没本事的时候,站谁那队都容易被当工具人;你有本事的时候,其实是别人来争着拉你进队。
换个角度想,空降领导来也要做业绩,他最需要的是“能干事、少惹事、关键时刻靠得住”的人,而不是只会喊口号的。与其急着表忠,或者赌气对着干,不如悄悄把自己的专业干硬,把底线守住,把选项留在自己手里。
面试题:每件商品的最新订单
我昨晚十一点多还在工位上磨这道题,饿得前胸贴后背,结果被产品经理一句话整清醒了: “东哥,我就想在后台上看到,每个商品最近一笔订单是啥,别给我整那些复杂报表。”
你们看,其实这道算法题翻译成人话就一句: 有一堆订单记录,每条上面有商品ID、下单时间啥的,要把每个商品“最新的一单”挑出来。
先把数据长啥样说清楚
脑子里先有个画面,不然写代码很抽象。比如我们有个订单列表:
订单1:productId = 1001, time = 10:01
订单2:productId = 1002, time = 10:05
订单3:productId = 1001, time = 10:08
订单4:productId = 1002, time = 09:59
...
那结果应该是:
商品 1001 → 选时间 10:08 的那条 商品 1002 → 选时间 10:05 的那条
也就是说,每个 productId 只留一条,时间最大那条。
我见我们组小李一开始写的是这种思路: 对每个商品,把所有订单再扫一遍,找时间最大的。
伪代码差不多这样(别抄啊,这个是“反例”):
for (每个商品 p) {
latest = null;
for (每个订单 o) {
if (o 的商品是 p 且 时间更新) {
latest = o;
}
}
}
问题也很明显: 订单有 n 条,商品有 m 个,这么一套下来时间复杂度 O(n * m),数据一大,服务器就开始喘粗气了。
线上的量级,谁敢这么写,监控一看QPS掉了,半夜就得被电话叫起来挨打。
正常人写法:扫一遍就搞定
你想啊,其实对每个商品,我们只需要记住“目前看到的最新那单”。 新的订单进来,只要比它晚,就把旧的替换掉。
所以更合理的做法是:
准备一个
Map<productId, 最新订单>顺序扫描所有订单:
map 里还没有这个商品:直接放进去 map 里已经有了:比较一下时间,新的更晚就替换
一趟搞定,时间复杂度 O(n),空间复杂度 O(m),非常稳。
先整一个订单类,简单点就行:
import java.time.LocalDateTime;
publicclassOrder{
private Long orderId;
private Long productId;
private LocalDateTime orderTime;
publicOrder(Long orderId, Long productId, LocalDateTime orderTime){
this.orderId = orderId;
this.productId = productId;
this.orderTime = orderTime;
}
public Long getOrderId(){
return orderId;
}
public Long getProductId(){
return productId;
}
public LocalDateTime getOrderTime(){
return orderTime;
}
@Override
public String toString(){
return"Order{" +
"orderId=" + orderId +
", productId=" + productId +
", orderTime=" + orderTime +
'}';
}
}
主逻辑用最直白的写法先来一版:
import java.time.LocalDateTime;
import java.util.*;
publicclassLatestOrderDemo{
publicstatic Map<Long, Order> findLatestOrderByProduct(List<Order> orders){
// key: 商品ID, value: 当前看到的最新订单
Map<Long, Order> latestMap = new HashMap<>();
for (Order order : orders) {
Long productId = order.getProductId();
Order cur = latestMap.get(productId);
if (cur == null) {
// 第一次见到这个商品
latestMap.put(productId, order);
} else {
// 比较下单时间,谁更新要谁
if (order.getOrderTime().isAfter(cur.getOrderTime())) {
latestMap.put(productId, order);
}
}
}
return latestMap;
}
publicstaticvoidmain(String[] args){
List<Order> list = Arrays.asList(
new Order(1L, 1001L, LocalDateTime.parse("2024-01-01T10:01:00")),
new Order(2L, 1002L, LocalDateTime.parse("2024-01-01T10:05:00")),
new Order(3L, 1001L, LocalDateTime.parse("2024-01-01T10:08:00")),
new Order(4L, 1002L, LocalDateTime.parse("2024-01-01T09:59:00"))
);
Map<Long, Order> latest = findLatestOrderByProduct(list);
latest.forEach((pid, o) -> System.out.println("商品 " + pid + " 最新订单:" + o));
}
}
这个跑出来,控制台会打印类似:
商品 1001 最新订单:Order{orderId=3, ...}
商品 1002 最新订单:Order{orderId=2, ...}
就达标了,对吧,很朴素,但是够用。
要是你喜欢 Java 8 的味道,也可以这么玩
有时候面试官会问:“能不能用 stream 写一下?” 其实就是把上面的逻辑塞进 Collectors.toMap 的合并函数里:
import java.util.function.Function;
import java.util.stream.Collectors;
publicstatic Map<Long, Order> findLatestOrderByProductStream(List<Order> orders){
return orders.stream()
.collect(Collectors.toMap(
Order::getProductId, // key: 商品ID
Function.identity(), // value: 当前这条订单
(o1, o2) -> // key 冲突时的合并逻辑
o1.getOrderTime().isAfter(o2.getOrderTime()) ? o1 : o2
));
}
这里有个小点很多人一开始会写错:toMap 如果 key 冲突,你不写第三个参数,它是会直接抛异常的,不会默默帮你选一个。 所以合并函数一定要写清楚,“旧订单 vs 新订单,谁时间晚要谁”。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html