面灵AI→

腾讯算法岗面经合集:感知哈希、RAG 与 Go 调度

轮次
多轮面试合集
时间
2026-09
来源
牛客网

《面试题目》

  1. (面经 01)算法:给定数组 a[1..n],可反复执行「所有 a[i] 减 i」或「所有 a[i] 减 n−i+1」,判断能否使数组全部变为 0;追问:给出的条件是否同时满足必要性和充分性,是否存在边界情况?
  2. (面经 01)算法:n 个 32 位整数中,一个数出现 1 次,其余数都出现 3 次,要求 O(n) 时间、O(1) 额外空间找出只出现一次的数;追问:若其余数出现 2 次应如何处理,是否有更优方法?
  3. (面经 01)算法:已知 rand5() 等概率返回 0~4,使用 rand5() 实现 rand3() 和 rand7();追问:调用 rand5() 的期望次数是多少?
  4. (面经 01)智力题:64 匹马、8 条跑道,每匹马能力固定,最少比赛多少轮可以决出前 4 名?
  5. (面经 02)请做一下自我介绍,并介绍当前实习岗位的主要工作
  6. (面经 02)算法:给定天数、目标距离和每天可走步数,求达到目标距离的方案数
  7. (面经 02)为什么在项目中引入感知哈希进行图片相似识别?
  8. (面经 02)汉明距离在图片相似识别中如何使用?
  9. (面经 02)感知哈希的阈值如何确定?
  10. (面经 02)感知哈希的大致原理是什么?
  11. (面经 02)RAG 采用什么方式进行召回?
  12. (面经 02)能力证据单元如何抽取?
  13. (面经 02)为什么不直接对整份简历做 Embedding?
  14. (面经 02)Redis 为什么使用单线程仍能获得较高吞吐?
  15. (面经 02)如果 Redis 命令改成多线程执行,会增加哪些开销?
  16. (面经 02)Go 协程如何实现?Goroutine 在什么情况下会发生调度切换?
  17. (面经 03)介绍你完成得最好的项目以及个人负责的部分
  18. (面经 03)项目效果具体提升了多少?
  19. (面经 03)为什么通用检索方案不能解决项目问题?
  20. (面经 03)你的方案与语义切块有什么区别?
  21. (面经 03)已有方案也能用模型补充上下文,你的方案有何不同?
  22. (面经 03)如何保证模型抽取结果的准确性?
  23. (面经 03)为什么不使用规则处理?
  24. (面经 03)项目中的排序指标表示什么?
  25. (面经 03)介绍一次处理线上故障的经历
  26. (面经 03)Go 的 Goroutine 底层如何调度?
  27. (面经 03)Goroutine 的初始栈有多大,如何扩容?
  28. (面经 03)Go GC 如何尽量降低 STW 时间?
  29. (面经 03)网络状况足够好时,TCP 和 UDP 传输同一个文件哪个更快,为什么?
  30. (面经 03)为什么已有 HTTP 仍需要 HTTPS?

《参考解析》

1. 其余数出现 3 次时找唯一数。不能只靠一次异或。主流做法有两种:①按位统计——把每个数的 32 个二进制位分别累加,每一位对 3 取模,剩下的位拼起来就是答案,时间 O(32n)、空间 O(1),最好写也最好讲;②用两个掩码模拟三进制计数:ones = (ones ^ x) & ~twos; twos = (twos ^ x) & ~ones;,遍历完 ones 就是答案,本质是把每个位的出现次数 mod 3 编码在两个 bit 里。追问「若其余数出现 2 次」就简单了——全部异或,成对的互相抵消,剩下的就是唯一数。由此可以总结出通用套路:「其余出现 k 次」这类题就是把每一位按模 k 计数。

2. rand5 实现 rand3 与 rand7 的期望调用次数。核心是拒绝采样。用两次 rand5() 拼出一个 0~24 的均匀随机数 r = 5 * rand5() + rand5()。实现 rand7:r < 21 时返回 r % 7(21 是 7 的倍数,保证均匀),否则重来,接受率 21/25;每次尝试要调 2 次 rand5,所以期望调用次数是 2 × 25/21 = 50/21 ≈ 2.38 次。实现 rand3:r < 24 时返回 r % 3,接受率 24/25,期望次数是 2 × 25/24 = 25/12 ≈ 2.08 次。面试时再补两句推广会更完整:k 次 rand5 能生成 5^k 个等概率值,所以调用次数越多可用的「余数空间」越大;绝对不能写成 (5 * rand5() + rand5()) % 7 这种直接取模,那会破坏均匀性。

3. 感知哈希做图片相似。pHash 的流程是「缩放到 32×32 灰度 → DCT 变换 → 取左上角 8×8 低频系数 → 去掉直流分量后与中位数比较 → 得到 64 位指纹」;aHash 是缩到 8×8 灰度后与均值比较;dHash 是比较相邻像素的梯度方向。相似度用汉明距离衡量(异或后数 1 的个数),阈值必须用业务样本标定而不是拍脑袋:收集「同一张图的不同版本」(压缩、缩放、加水印)和「不同图」两组样本,画出汉明距离分布,取两类分布的分界点,工程上 64 位指纹常用 5~10 这个区间,阈值越小越严(漏判多),越大越松(误判多)。感知哈希的优点是快、对缩放压缩和轻微改色鲁棒;缺点是对大幅裁剪、旋转和强滤镜不鲁棒,也不表达语义相似——内容不同但构图相似的图会被判为相似,要做语义级相似得上 CLIP 这类 embedding 模型。

4. 为什么不直接对整份简历做 Embedding。整份简历是多主题长文本——个人信息、教育、技能、多段实习、项目、获奖混在一起,直接 embedding 会把这些语义平均成一个向量,检索粒度太粗:一句「用过 PyTorch」和「主导过推荐系统」被压进同一个向量,相似度就失去了区分度。做法是先抽「能力证据单元」,把简历切成「技能 + 行为 + 量化结果 + 时间/角色」的原子条目(例如「用 XGBoost 把流失预测 AUC 从 0.72 提到 0.81」),每条单独 embedding,检索时按 query 召回具体证据再拼装上下文——好处是召回可解释、可加权、可去重,更新也只影响单条。抽取准确性靠「模型抽 + 规则校验 + 人工抽检」:用 JSON Schema 一类结构化输出约束格式,对数字和时间做规则校验,低置信度样本走二次模型复核或人工。召回方式上,简历里全是框架名、公司名、指标名这类专有名词,纯向量召回不敏感,标准组合是「向量检索 + BM25 关键词检索 + 重排」。

5. Redis 单线程为什么快、改多线程会增加什么开销。快的原因有五个:纯内存操作没有磁盘 IO;单线程没有锁竞争和上下文切换;IO 多路复用加事件驱动,把「等网络」和「执行命令」解耦,网络慢不会阻塞命令执行;数据结构和编码针对性强(listpack、intset、跳表);命令本身都是 O(1) 或 O(log n) 的小操作,单次延迟在微秒级。把命令执行改成多线程要付的代价是:①必须加锁保护共享数据结构(键空间、过期字典、统计),锁竞争会吃掉并行收益;②原子性语义变复杂——MULTI、Lua 脚本、INCR、WATCH 这些依赖单线程串行的语义都要重新定义;③CPU 缓存局部性变差、伪共享增多;④上下文切换与调度开销;⑤并发 bug 的复现和调试成本。所以 Redis 6 的多线程只用在网络 IO 读写和协议解析上,命令执行仍是单线程;真正的横向扩展手段是分片(Cluster)和读写分离。

6. Goroutine 调度、栈扩容与 Go GC 降 STW。调度是 GMP 模型:G 是 goroutine(初始栈 2KB,按需以约 2 倍扩容——旧栈内容复制到新栈并修正指向栈的指针,栈收缩放在 GC 时做),M 是 OS 线程,P 是处理器上下文(数量默认 GOMAXPROCS,持有长度 256 的本地运行队列)。M 必须绑定 P 才能执行 G;本地队列空了会从全局队列取,或从别的 P 偷一半(work stealing);G 做阻塞系统调用时 M 与 P 解绑,P 交给其他 M 继续跑,避免线程被 IO 拖死。抢占在 1.14 之前是协作式的(只在函数调用检查点让出),长循环会霸占 P,1.14 起用基于信号的异步抢占,sysmon 发现某个 G 运行过久就发信号。GC 是三色标记加混合写屏障的并发标记清除:标记与用户代码并发,靠写屏障保证不漏标;STW 只留在「标记开始前的准备」和「标记结束」两个极短阶段,把主要工作放到并发阶段和后台 mark worker。降低 STW 的手段还有:把大对象清扫和栈扫描分散到多个周期、用 GOGC 调整触发频率、用 GOMEMLIMIT(1.19+)限制内存上限、减少大对象和长生命周期对象的创建以缩小标记工作量;调优要靠 GODEBUG=gctrace=1 和 pprof 看 GC 次数与暂停分布,而不是凭感觉改 GOGC。