面灵AI→

新凯来线下二面:项目难点深挖、RRF 与手撕算法

轮次
二面
时间
2026-10
来源
牛客网

《面试题目》

  1. 自我介绍。
  2. 讲一下你这两个项目里最难的一个技术点,它困扰了你多久?
  3. 你是怎么解决的,从哪些方面考虑的?
  4. 这个点有没有想过是别的模块引起的,有没有做全链路追踪排查?
  5. 你说的这个 RRF 是什么,解决了什么问题,内部原理是什么?
  6. 最终效果怎么样,评测集你是怎么定的?
  7. 做点 leetcode 题。
  8. 如何判断链表有环?
  9. 如何判断环的位置在哪?
  10. 除了快慢指针,还有什么别的方法可以做这道题?
  11. 如何找出两个字符串的最长公共子串?
  12. 时间复杂度是多少,最坏情况下呢?
  13. 还有什么方法?
  14. C++ 和 Python 会吗?
  15. 讲讲 JVM 的结构,各个部分什么作用以及数据结构。
  16. 说说垃圾回收。
  17. 讲一下你最有成就感的事情,怎么做到的?
  18. 有女朋友吗?
  19. 意向地和期望薪资?
  20. 你能接受的最大工作强度是多少?
  21. 反问业务。

《参考解析》

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,停顿与吞吐不可兼得),回答就有层次了。