被公司辞退了,领到了22万补偿金。结果准备入职下一家公司时,原公司HR电话叫我回去,涨薪7000,但要把赔偿金还回去
刚看到个贴子,说一哥们被公司辞退拿了22万补偿,结果下一家公司都谈好准备入职了,原公司HR突然杀回马枪:开口涨薪7000,但补偿金要还回去。
这事吧…本质就是公司发现离不开人了,但又想把成本压到最低,说白了就是“既要马儿跑,又要马儿不吃草”。
网友们有人说“快跑”,也有人觉得“钱到位就行”。我看了看,嗯…都各有道理。
从我的角度看,这种回头找你的操作,诚意是关键。如果真觉得你有价值,就应该在你被裁的时候说清楚,而不是等你找到下家了再来“收回一半好处”。
不过话说回来,职场嘛,都是算账的地方。你要是真看重涨薪,也能接受退钱,那就理性谈,合同白纸黑字写清楚;要是不想被反复横跳折腾,那就继续走你的。
机会很多,但自尊值钱,选择让自己心安的那条路就行。
面试题:设计日志存储系统
先说结论哈,这题其实不难,本质就是一句话:把一堆带时间戳的日志存起来,然后按时间范围 + 精度去查。难点不在算法多花哨,而是细节别翻车。
一般题目长这样(简化版):
日志有 id 和时间戳,时间格式是:
YYYY:MM:DD:HH:MM:SS你要实现两个方法:
put(int id, String timestamp):存一条日志retrieve(String start, String end, String gra):根据时间范围[start, end],加上一个“粒度”(比如到年、到月、到天…),返回所有在这个范围内的 id
粒度一般是:"Year" / "Month" / "Day" / "Hour" / "Minute" / "Second"。
听起来有点绕,但想象一下: “查 2017 年 1 月这个月的日志”,那我其实就是把时间都截成 “到月”,然后比大小对吧。
核心点:时间戳怎么跟“粒度”打配合?
这个时间格式很乖:YYYY:MM:DD:HH:MM:SS固定长度,补零,对字符串排序是等价于按时间排序的(这一点很关键)。
比如说有两个时间:
2017:01:01:23:59:59
2017:02:01:00:00:00
直接用字符串比较大小,结果和按时间比较一模一样,不用自己写什么时间解析、转 long 之类的,省很多事。
那粒度是干嘛的? 比如用户传:
start = 2017:01:01:23:59:59
end = 2017:12:31:00:00:00
gra = Year
如果按“年”来算,我们只关心前 4 位,后面全部按最小 / 最大补齐就行了:
对 start:按年 -> 变成 2017:00:00:00:00:00(最小可能)对 end:按年 -> 变成 2017:12:31:23:59:59(最大可能)
这样一来,问题就变成:给定一堆字符串形式的时间戳,查在 [start', end'] 之间的日志。
存储结构怎么选?
最直接的思路:
所有日志塞进一个 List每条日志存成一个小对象 {id, timestamp}每次 retrieve时,线性扫一遍,把在范围里的挑出来
这么干时间复杂度是 O(N) 一次查询,如果日志特别多、查询也多,就有点顶不住了。
稍微正规一点的做法:
我们让日志按时间有序
用 Java 自带的
TreeMap<String, List<Integer>>key:完整时间戳字符串 value:这一个时间戳下的所有 id(可能同一秒多条日志)
有序之后,我们就能用 subMap(startKey, true, endKey, true) 一把取出中间那段时间的所有日志,复杂度大概是 O(logN + K)(K 是命中的条数),会比无脑扫一遍好不少。
粒度对应的“前缀长度”
我们要做的,其实就是: 根据粒度,把字符串“截断”到对应位置,然后补上最小/最大值。
可以先做一个粒度到下标的映射(注意下标是“字符串位置”而不是字段个数):
Year-> 4("YYYY")Month-> 7("YYYY:MM")Day-> 10Hour-> 13Minute-> 16Second-> 19(整个串)
这样 start 和 end 处理方式是:
prefix = 原时间戳.substring(0, idx)
startBound = prefix + 最小尾巴
endBound = prefix + 最大尾巴
比如粒度是 Day,idx = 10:
最小尾巴: :00:00:00最大尾巴: :23:59:59
这样就能把“按天查询”的区间,映射成完整时间串的区间。
代码实现(Java)
直接上一个比较标准、能用的实现:
import java.util.*;
publicclassLogSystem{
// 按时间有序存日志
private TreeMap<String, List<Integer>> logs = new TreeMap<>();
// 粒度 -> 截取长度
privatestaticfinal Map<String, Integer> GRA_INDEX = new HashMap<>();
static {
GRA_INDEX.put("Year", 4);
GRA_INDEX.put("Month", 7);
GRA_INDEX.put("Day", 10);
GRA_INDEX.put("Hour", 13);
GRA_INDEX.put("Minute", 16);
GRA_INDEX.put("Second", 19);
}
publicLogSystem(){}
// 存一条日志
publicvoidput(int id, String timestamp){
logs.computeIfAbsent(timestamp, k -> new ArrayList<>()).add(id);
}
// 按时间范围 + 粒度查
public List<Integer> retrieve(String start, String end, String gra){
int idx = GRA_INDEX.get(gra);
String startPrefix = start.substring(0, idx);
String endPrefix = end.substring(0, idx);
// 根据粒度补齐最小 / 最大时间
String startKey = startPrefix + getMinTail(idx);
String endKey = endPrefix + getMaxTail(idx);
// TreeMap 子区间查询
NavigableMap<String, List<Integer>> sub =
logs.subMap(startKey, true, endKey, true);
List<Integer> res = new ArrayList<>();
for (List<Integer> ids : sub.values()) {
res.addAll(ids);
}
return res;
}
// 不同截断位置,对应的最小尾巴
private String getMinTail(int idx){
switch (idx) {
case4: return":00:00:00:00:00"; // 年
case7: return":00:00:00:00"; // 月
case10: return":00:00:00"; // 日
case13: return":00:00"; // 时
case16: return":00"; // 分
case19: return""; // 秒本身就完整
default: thrownew IllegalArgumentException("idx = " + idx);
}
}
// 不同截断位置,对应的最大尾巴
private String getMaxTail(int idx){
switch (idx) {
case4: return":12:31:23:59:59";
case7: return":31:23:59:59";
case10: return":23:59:59";
case13: return":59:59";
case16: return":59";
case19: return"";
default: thrownew IllegalArgumentException("idx = " + idx);
}
}
}
这个写法有几个小点可以注意:
时间戳直接用字符串因为格式稳定,所以不必转成 long 或
LocalDateTime,省转换成本,字符串比较就够用了。多个日志同一时间戳用
List<Integer>存 id,一次性都吐出去。时间复杂度
put():O(logN)(往 TreeMap 插入)retrieve():O(logN + K),K是命中的日志条数。
还能怎么优化?
如果你知道所有日志都会一次性给你,后面只查不再新增,可以:
先把所有日志放 List按时间排序 查询时用二分查上下界,就不要 TreeMap 了
但一般面试里,设计成现在这种“随时 put,随时查”更符合题意,用 TreeMap 比较顺手。
大概就这样,这道题关键就是认清几点:
时间戳是“字符串可排序”的 粒度 = 截断 + 补最小/最大尾巴 有序结构上用区间查询,就非常自然了
你真要写的时候,别在时间解析和边界判断上纠结太久,这种题面试官主要看你思路是否清晰、数据结构选得顺不顺。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html