面灵AI→

字节后端二面:Agent 优化、ReAct 伪代码与四道手撕

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

《面试题目》

  1. 讲一下你这些实习和个人项目中最有价值的一个项目,亮点在哪?
  2. Agent 底层做了哪些优化,举个例子?
  3. 说一下 ReAct,并且写出 ReAct 的伪代码?
  4. 一段代码做 CR,找出问题、修改代码、运行成功,并分析时空复杂度?
  5. HashMap 为什么 get 是 O(1)?
  6. 桶的数据结构是怎样的,数组为什么能做到是 O(1)?
  7. 讲一下数组 get 的过程,有哪些步骤?
  8. MySQL 深分页优化的手段?

手撕四题

  1. 手撕 ReAct 伪代码,并讲清思路
  2. 代码 CR(线程池相关)
  3. MySQL 深分页优化
  4. 手撕:找到第 n 个素数

《参考解析》

HashMap 为什么 get 是 O(1),以及数组为什么也能 O(1)。 这里面试官连着问了两题,其实是在探你分不分得清「哈希」和「寻址」。数组的 O(1) 来自随机访问的地址计算:base + index * sizeof(T) 一条指令算出来,与元素个数无关——前提是内存连续、元素等长。HashMap 的 O(1) 是期望复杂度:先用 hash(key) 算出哈希值,再对桶数组长度取模(JDK 里因为容量是 2 的幂,用 (n-1) & hash 替代取模)定位到桶,这一步和数组寻址一样是常数时间;桶里的链表或红黑树才是决定最坏情况的部分——冲突少时链表长度是常数,冲突极端时退化为 O(n),JDK 1.8 引入红黑树把最坏压到 O(log n)。两个必要补充:哈希函数的目的是让 key 尽量均匀分布,所以自定义对象必须同时重写 hashCode 和 equals;容量取 2 的幂是为了让 (n-1) & hash 等价于取模且分布均匀。

MySQL 深分页为什么慢、怎么优化。 LIMIT offset, size 的语义是「扫描并丢弃前 offset 行」,所以 LIMIT 1000000, 20 要让存储引擎取出 1000020 行、再扔掉前 100 万行——慢的不是返回的 20 行,而是被丢弃的部分。而且如果走二级索引还要回表,成本更上一层。优化按代价从低到高:① 游标分页,用上一次结果的最大排序键做锚点(WHERE id > :last_id ORDER BY id LIMIT 20),把偏移量换成范围条件,索引可以直接定位,是首选;排序键不唯一时用「(a, b) 的元组比较」写法保证不跳行不漏行。② 延迟关联,先在覆盖索引上分页拿主键,再回表取整行(SELECT * FROM t JOIN (SELECT id FROM t ORDER BY ... LIMIT 1000000, 20) x USING(id)),把回表次数从 100 万降到 20。③ 业务上限制最大可翻页数,或把深翻需求改成条件筛选/搜索。④ 只查总数用于展示时,COUNT(*) 往往比列表本身还慢,可以用近似值或单独的计数表。

Agent 的底层优化该怎么说。 这道题问的是「优化」而不是「架构」,所以要落到具体机制和数字上,常见的言之有物的方向有五个:上下文管理(长工具输出外置成文件只留摘要、对历史做分段摘要、把稳定前缀放在最前面以命中 prompt cache);工具调用(并行化无依赖调用、把串行编排改成状态机、给工具加重试与幂等键、对同一工具同参数做去重熔断);模型侧(按任务难度分级选模型和 thinking 预算、结构化输出用约束解码而不是靠提示词祈祷);延迟(把中间结果流式透出、长任务异步化并回报进度);成本(命中缓存的 token 比例、单任务平均 token 与失败重试率)。回答时至少给出一个「优化前 vs 优化后」的对比数字,否则听起来只是罗列。

ReAct 伪代码该怎么写。 核心就是「想 → 做 → 看」的循环,面试时要同时写出终止条件和上下文管理,这两点才是加分项:

messages = [system_prompt, user_goal]
for step in 1..max_steps:
    out = llm(messages, tools)          # 一次调用同时可能返回文本和 tool_calls
    if out.has_tool_calls:
        for call in out.tool_calls:      # 无依赖的可以并行执行
            result = execute(call)       # 带超时、幂等键、错误归因
            messages.append(tool_result(call, truncate(result)))
        continue
    if out.text: return out.text         # 模型给出最终答案
    break                                # 既无工具调用也无文本 = 异常,退出
return fallback("达到最大步数,已完成:…,未完成:…")

要主动补的三点:终止条件不能只靠最大步数,还应有「连续 N 轮没有新信息」「同一工具同参数重复调用」的熔断,以及命中不可恢复错误时转人工;上下文要外置,长工具结果不进 messages 全文而只留摘要或文件句柄;可观测性,每一步的思考、调用、结果都要落 trace,否则线上出问题无法复盘。

线程池代码 CR 的常见考点。 给一段线程池代码让你 CR,问题通常埋在这几处:队列用了 LinkedBlockingQueue 无界(maximumPoolSize 形同虚设,任务堆积到 OOM);用了 Executors.newFixedThreadPool/newCachedThreadPool 这类快捷方法;拒绝策略是静默丢弃(应该用 CallerRunsPolicy 做反压,或至少打日志+告警);没有自定义 ThreadFactory(线程名默认 pool-N-thread-M,线上 dump 出来找不着人);任务里吞异常(submit 返回的 Future 不 get 时异常会被静默吃掉);shutdown() 之后没 awaitTermination 也没有兜底 shutdownNow();共享可变状态没有同步。答题结构建议先讲资源边界(队列/线程数/内存),再讲错误处理(拒绝、异常、超时),最后讲可观测性(命名、指标、优雅关闭)。

求第 n 个素数的思路。 朴素做法是逐个试除到 sqrt(x),复杂度约 O(n·√p),n 很大时不够。更稳的是埃氏筛:预处理到某个上界(第 n 个素数约等于 n·ln n,估上界时留 1.5 倍余量),复杂度 O(N log log N)。若 n 极大需要分段筛或线性筛。写的时候注意边界:n = 1 返回 2,n = 0 无意义要提前拒绝;试除只需到 i * i <= x,且先排除偶数再只试奇数因子可以省一半时间。