新凯来线下二面:项目难点深挖、RRF 与手撕算法
- 轮次
- 二面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 讲一下你这两个项目里最难的一个技术点,它困扰了你多久?
- 你是怎么解决的,从哪些方面考虑的?
- 这个点有没有想过是别的模块引起的,有没有做全链路追踪排查?
- 你说的这个 RRF 是什么,解决了什么问题,内部原理是什么?
- 最终效果怎么样,评测集你是怎么定的?
- 做点 leetcode 题。
- 如何判断链表有环?
- 如何判断环的位置在哪?
- 除了快慢指针,还有什么别的方法可以做这道题?
- 如何找出两个字符串的最长公共子串?
- 时间复杂度是多少,最坏情况下呢?
- 还有什么方法?
- C++ 和 Python 会吗?
- 讲讲 JVM 的结构,各个部分什么作用以及数据结构。
- 说说垃圾回收。
- 讲一下你最有成就感的事情,怎么做到的?
- 有女朋友吗?
- 意向地和期望薪资?
- 你能接受的最大工作强度是多少?
- 反问业务。
《参考解析》
RRF 解决的是「不同召回路的分数不可比」。 RRF(Reciprocal Rank Fusion,倒数排名融合)的公式是 score(d) = Σ 1/(k + rank_i(d)),rank_i(d) 是文档在第 i 路召回里的名次(从 1 开始),常数 k 常取 60。它被发明出来,是因为向量召回给的是余弦相似度、BM25 给的是词频权重分,两者量纲与分布完全不同,直接相加等于让某一路主导。RRF 只用名次、不用分数,所以免掉了归一化和调参。代价是丢掉了分数的「幅度信息」——第一名遥遥领先和险胜在 RRF 里是同一档;当某一路明显更可靠时,可以改用加权 RRF(每路乘权重),或先做 min-max/z-score 归一化再加权求和。工程细节也要说得出:多路结果先按文档 ID 去重再累加、k 越小越强调头部、并列名次处理要统一。验证效果别只凭观感——固定候选池做离线回放,用人工标注的 query→相关文档算 Recall@k、MRR、nDCG,对比融合前后的同一指标,这样「最终效果怎么样」才有依据。
「最难的技术点」按现象—定位—验证—收益讲,并且答得住跨模块归因。 面试官问「困扰你多久」「有没有想过是别的模块引起的」,考的是排查方法而不是结论:先用可观测证据把问题切成段(traceId 串起全链路、各层加分位计时、看 P50/P95/P99 而不是单点均值),再对最可疑的一段做隔离验证——关掉缓存、把下游打桩、把并发降到 1,用「如果假设成立,应该观察到什么」来证伪;不要一上来就说「我猜是 X 模块」。拿到收益要有前后对比(耗时、命中率、失败率、成本),并说清代价(复杂度、内存、一致性风险),最后补一句这次沉淀了什么可复用的东西(监控规则、排查脚本、文档),这才是「闭环」。
链表判环与找环入口,要给得出证明,也要给得出替代解法。 判环用 Floyd 快慢指针,快指针每次两步、慢指针一步,相遇即有环,时间 O(n)、空间 O(1)。找入口:相遇后把一个指针挪回头节点,两个指针同速前进,再次相遇的节点就是入口。证明思路要能顺口说:设头到入口长 a、入口到相遇点长 b、相遇点绕回入口长 c,慢指针走 a+b,快指针走 a+b+n(b+c),由 2(a+b)=a+b+n(b+c) 得 a = c + (n−1)(b+c),所以从头走 a 步与从相遇点走 c 步会同时落到入口。替代解法:哈希表存访问过的节点(O(n) 空间但不动结构,最稳);反转指针法(边走边改 next,需要恢复链表);Brent 算法(步长倍增,移动次数更少);标记节点法(只在不介意改节点的题里能用)。常被顺手追问的还有「怎么求环长」——相遇后让一个指针绕一圈计数即可。
最长公共子串先确认是子串还是子序列。 子串要求连续,转移方程是 s[i]==t[j] ? dp[i−1][j−1]+1 : 0,取全局最大值;不相等时必须清零,这正是和最长公共子序列的分水岭(子序列保留前缀最优解)。时间 O(nm),空间可以用滚动数组压到 O(min(n,m))。其他解法:后缀自动机 O(n+m)(最难写但最优)、二分答案 + 字符串哈希 O((n+m)·log min)(工程上最常用,注意哈希冲突可双哈希)、暴力枚举起点 O(n·m·min(n,m))(短串够用)。追问「最坏情况」时要分清:DP 恒为 O(nm),暴力最坏是 O(n·m·min),哈希法期望线性但最坏会退化,且要考虑构造数据的哈希抗碰撞。
JVM 按「内存分区—存活判定—回收算法—收集器取舍」四层答。 分区:堆(新生代 Eden 与两块 Survivor、老年代)、方法区(JDK 8 起是元空间,本地内存)、虚拟机栈(栈帧:局部变量表、操作数栈、动态链接、返回地址)、本地方法栈、程序计数器、直接内存。判定存活用可达性分析,GC Roots 包括栈上引用、静态变量、常量、JNI 引用;不用引用计数是因为循环引用收拾不了。回收算法:标记-清除(碎片)、复制(新生代,Eden:Survivor 默认 8:1:1,浪费空间但快)、标记-整理(老年代)。实现细节值得主动提:TLAB 加速分配、大对象直接进老年代、跨代引用靠卡表与记忆集、G1 把堆切成 Region 做可预测停顿、ZGC/Shenandoah 靠染色指针与读屏障做低停顿。面试官追问「各部分的数据结构」时,就落到 Eden/Survivor 的划分、卡表与记忆集、G1 的 Region 与优先回收列表这些具体实现上;再补一句收集器取舍(吞吐优先选 Parallel,低延迟选 G1/ZGC,停顿与吞吐不可兼得),回答就有层次了。