我在网上看到个帖子,真是看得我这个程序员后背一紧。
有人吐槽说,今天面了个28岁的候选人,某大厂T5,开口预期65k。
关键你还很难反驳,人家简历是真能打:985硕士,4年经验,做过核心项目,数据也漂亮,面下来脑子清楚,技术不虚,对业务还门儿清。
你说65k离谱吧,人家能力摆在那儿;你说合理吧,旁边32岁的老同事估计已经默默打开招聘软件了
最扎心的地方就在这儿。现在职场不是谁年龄大谁值钱,是谁能直接解决问题谁值钱。
说白了,大厂光环只是门票,真把价码抬起来的,还是项目、产出和脑子。对打工人来说,这玩意儿残酷归残酷,倒也公平——前提是别一边喊卷,一边技术栈还停留在“我也会一点”。
面试题:日志速率限制器
这道“日志速率限制器”看起来不难,但真写代码时,很多人会卡在一个地方:到底要不要真的把 10 秒内的所有日志都存下来。
题目的规则很直接:给你一条日志消息和当前时间戳,如果这条消息在过去 10 秒内已经打印过,那这次就不能再打印;如果没打印过,就返回 true,并记录这次打印时间。
先看最直接的做法。 我当时第一反应不是队列,也不是滑动窗口,就是一个 HashMap,key 放消息内容,value 放这条消息上次成功打印的时间。
import java.util.HashMap;import java.util.Map;publicclassLogger{privatefinal Map<String, Integer> lastPrintTime = new HashMap<>();publicbooleanshouldPrintMessage(int timestamp, String message){ Integer lastTime = lastPrintTime.get(message);if (lastTime == null) { lastPrintTime.put(message, timestamp);returntrue; }if (timestamp - lastTime >= 10) { lastPrintTime.put(message, timestamp);returntrue; }returnfalse; }}
这个解法其实已经够用了,而且很贴题。
比如这样走一遍:
Logger logger = new Logger();System.out.println(logger.shouldPrintMessage(1, "foo")); // trueSystem.out.println(logger.shouldPrintMessage(2, "bar")); // trueSystem.out.println(logger.shouldPrintMessage(3, "foo")); // falseSystem.out.println(logger.shouldPrintMessage(11, "foo")); // true
这里真正要抓住的是: 我们并不关心某条日志 10 秒内出现了几次,只关心它最近一次被允许打印的时间。 一旦明白这一点,存一堆历史记录就显得多余了。
这个写法时间复杂度是 O(1),空间复杂度是 O(n),n 是出现过的不同消息数。 面试里一般到这一步就够了。
但如果题目再追问一句:消息种类特别多怎么办?Map 会不会越来越大?这时候就不能只停留在“能过题”了。
比如线上系统里日志 key 如果带请求参数,"order_1001_fail"、"order_1002_fail" 这种一路涨上去,Map 迟早会肥起来。那就要考虑清理老数据。可以顺手补一个近似淘汰逻辑:
import java.util.Iterator;import java.util.Map;import java.util.HashMap;publicclassLoggerWithClean{privatefinal Map<String, Integer> lastPrintTime = new HashMap<>();publicbooleanshouldPrintMessage(int timestamp, String message){ clean(timestamp); Integer lastTime = lastPrintTime.get(message);if (lastTime == null || timestamp - lastTime >= 10) { lastPrintTime.put(message, timestamp);returntrue; }returnfalse; }privatevoidclean(int timestamp){ Iterator<Map.Entry<String, Integer>> it = lastPrintTime.entrySet().iterator();while (it.hasNext()) { Map.Entry<String, Integer> entry = it.next();if (timestamp - entry.getValue() >= 10) { it.remove(); } } }}
不过这段清理代码就有点“工程味”了,面试标准答案反而未必喜欢,因为每次都清理会把 O(1) 拉高。 所以如果只是算法题,前一个版本更稳。
这题有意思的地方,不在代码多复杂,而在于你能不能很快把问题收缩成一句话:
同一条消息,只需要记住上一次成功打印的时间。
一旦收缩到这里,代码基本就出来了。很多限流题也是这个路子,别一上来就想得太大,先看题目真正卡你的状态量到底有几个。 这题最后落下来,其实就一个。