🎯 参考回答 · 算法 / CS 基础篇(17 题 · C 级但不过不行)

4793 words
24 minutes
🎯 参考回答 · 算法 / CS 基础篇(17 题 · C 级但不过不行)

🎯 参考回答 · 算法 / CS 基础篇(17 题 · C 级但不过不行)#

📅 生成日期:2026-06-16
📚 数据来源:工作区面经 + 秋招复习笔记 + 经典八股整理
🎯 适用:AI 应用开发 / Agent 开发 面试 — 非算法岗但面试必考


⚠️ 重要提醒#

这部分的题目不是靠”背”就能过的——面试官一看就知道你有没有真理解。每道题不仅要会回答,还要能应对追问。

对于 Agent 岗位的面试,这部分题目的重要性排序:

  • 高频追击区(面试官最爱考的):线程池、HashMap、单例模式、SSE vs WebSocket、注意力机制
  • 必保底区(不过不行):合并有序数组、字符串相加、JVM类加载、HTTP 版本区别
  • 加分项区(体现深度):手写死锁、MySQL 两阶段提交、AQS

一、🖥 手撕代码 · 高频题(5 题)#


题1:单例模式(双检锁 + volatile)#

参考回答(面试口语化 + 手写代码):

“单例模式确保一个类只有一个实例,双检锁(Double-Checked Locking)是线程安全的懒加载实现。”

public class Singleton {
// volatile 防止指令重排序导致其他线程拿到未初始化完成的对象
private static volatile Singleton instance;
private Singleton() {} // 私有构造
public static Singleton getInstance() {
if (instance == null) { // 第一重检查:避免不必要的同步
synchronized (Singleton.class) {
if (instance == null) { // 第二重检查:确保只有一个线程创建实例
instance = new Singleton();
// new Singleton() 的三步:
// 1. 分配内存空间
// 2. 初始化对象
// 3. 将引用指向内存地址
// volatile 禁止 2 和 3 重排序
}
}
}
return instance;
}
}

面试追问准备:

  • 为什么用 volatile? → 防止 new Singleton() 的指令重排序,避免拿到”半初始化”的对象
  • 为什么两次检查? → 第一次避免每次都进入同步块(性能),第二次确保只有一个线程创建
  • 还有哪些实现方式? → 饿汉式(类加载时初始化)、静态内部类(利用类加载机制)、枚举(最简洁安全)

题2:合并两个有序数组#

参考回答(面试口语化 + 手写代码):

“经典的双指针从后往前问题,避免覆盖。”

public void merge(int[] nums1, int m, int[] nums2, int n) {
// 从后往前放最大的元素
int p1 = m - 1; // nums1 有效元素的最后一个
int p2 = n - 1; // nums2 的最后一个
int p = m + n - 1; // 合起来的总长度
while (p2 >= 0) {
if (p1 >= 0 && nums1[p1] > nums2[p2]) {
nums1[p] = nums1[p1];
p1--;
} else {
nums1[p] = nums2[p2];
p2--;
}
p--;
}
}

面试追问准备:

  • 为什么从后往前? → nums1 后面有空位,从后往前不会被覆盖
  • 时间复杂度? → O(m+n)
  • 空间复杂度? → O(1),原地修改

字节面试经验:面试官说”好,你刷过,下一个追问”——所以答完要准备好被追问其他不刷题就不知道的问题。


题3:字符串相加#

参考回答(面试口语化 + 手写代码):

大数加法,用模拟竖式加法的方式,从个位开始逐位相加。”

def addStrings(num1: str, num2: str) -> str:
i, j = len(num1) - 1, len(num2) - 1
carry = 0
res = []
while i >= 0 or j >= 0 or carry:
x = int(num1[i]) if i >= 0 else 0
y = int(num2[j]) if j >= 0 else 0
sum_val = x + y + carry
carry = sum_val // 10 # 进位
res.append(str(sum_val % 10)) # 当前位
i -= 1
j -= 1
return ''.join(reversed(res))

面试追问准备:

  • 不能把字符串转整数直接加吗? → 因为字符串可能很长,超过整数范围(大数问题)
  • 时间复杂度? → O(max(m, n))
  • 能处理负数吗? → 需要额外判断符号位,逻辑更复杂

题4:手写一个死锁#

参考回答(面试口语化 + 手写代码):

“死锁产生需要四个条件:互斥、持有并等待、不可剥夺、循环等待。破坏任何一个就能解除死锁。手写一个死锁演示:”

public class DeadlockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
// 线程1:先拿 lock1,再拿 lock2
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread1: 拿到了 lock1");
try { Thread.sleep(100); } catch (Exception e) {}
synchronized (lock2) {
System.out.println("Thread1: 拿到了 lock2");
}
}
});
// 线程2:先拿 lock2,再拿 lock1(和线程1顺序相反,死锁)
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread2: 拿到了 lock2");
try { Thread.sleep(100); } catch (Exception e) {}
synchronized (lock1) {
System.out.println("Thread2: 拿到了 lock1");
}
}
});
t1.start();
t2.start();
}
}

面试追问准备:

  • 怎么避免死锁? → 破坏四个条件之一,最常用的是”破坏循环等待”——所有线程按固定顺序拿锁
  • 怎么排查死锁?jstack 看线程 dump,或使用 jconsole / VisualVM 检测
  • Java 层面有自动解除死锁的机制吗?Lock 接口的 tryLock(long timeout) 尝试拿锁,拿不到就释放已持有的锁

题5:Todo Agent 实现(AI Coding 环节)#

参考回答(面试口语化 + 思路):

“Todo Agent 是一个任务管理 + 自动执行的 Agent 系统,用户说’帮我整理代码,写测试,然后部署’,Agent 分析需求→拆解任务→依次执行→反馈结果。”

核心设计思路(代码不是关键,架构思路是):

1. 任务解析层:理解用户输入,拆分成子任务列表
2. 任务调度层:决定执行顺序(串行/并行/依赖关系)
3. 执行层:每个子任务 → 工具调用 → 结果校验
4. 状态管理层:记录任务进度,支持中断恢复
5. 反馈层:执行结果总结,通知用户

能力边界:

  • 能做什么:自动化执行多步骤任务(编码→测试→部署)
  • 不能做什么:无法自动决策复杂需求,需要人工确认关键节点

加分回答:把 Todo Agent 和你做的项目的 Agent 架构联系起来——“我们在 code agent 里已经实现了类似的任务拆解和调度机制……”


二、☕ Java / 语言基础 · 高频八股(8 题)#


题6:线程池核心参数,怎么设计一个合适的线程池?#

参考回答(面试口语化——不能只会背,要能设计):

核心参数(7个):

new ThreadPoolExecutor(
corePoolSize, // 核心线程数(一直存活)
maximumPoolSize, // 最大线程数
keepAliveTime, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
workQueue, // 阻塞队列(存放等待任务)
threadFactory, // 线程工厂(命名规则)
handler // 拒绝策略
);

怎么设计? 两个关键决策:

① 核心线程数 = CPU 密集型还是 IO 密集型?

  • CPU 密集型(计算为主):核心线程数 = CPU 核数 + 1
  • IO 密集型(网络/磁盘等待多):核心线程数 = CPU 核数 × 2(或者更多)
  • 混合型:拆成不同线程池分别管理

② 拒绝策略怎么选?

  • AbortPolicy(默认):抛异常——适合不能丢任务的场景
  • CallerRunsPolicy:让提交任务的线程自己跑——适合”慢下来也不要丢”
  • DiscardPolicy / DiscardOldestPolicy:丢任务——适合可丢的场景

面试加分:结合实际场景——“我们 code agent 的 LLM 调用是 IO 密集型(网络请求+等待返回),所以线程池设的比较大。但工具调用中如果包含本地计算,走独立的小线程池。“


题7:HashMap 底层结构,冲突怎么处理?#

参考回答(面试口语化):

底层结构:

  • JDK 1.8 之前:数组 + 链表
  • JDK 1.8 之后:数组 + 链表 / 红黑树
put(key, value) 过程:
1. 对 key 的 hashCode() 做二次 hash(扰动函数)
2. 用 hash & (n-1) 定位到数组槽位
3. 如果槽位为空,直接放
4. 如果槽位不为空 → 发生 hash 冲突 → 用链表/红黑树处理

冲突处理:拉链法(链地址法)

  • 链表法:冲突元素挂链表上,遍历链表查找
  • 当链表长度 ≥ 8 且数组长度 ≥ 64 → 转红黑树(O(n) → O(log n))
  • 当红黑树节点 ≤ 6 → 转回链表

扩容:

  • 默认负载因子 0.75——空间与时间的权衡
  • 扩容时容量翻倍,所有元素重新计算位置(rehash)
  • 扩容是性能瓶颈,所以预估容量设初始值很重要

面试加分追问:HashMap 线程安全吗?→ 不安全,并发 put 可能导致死循环(JDK1.7 头插法)或数据覆盖。用 ConcurrentHashMap(分段锁/CAS+ synchronized)。


题8:JVM 类加载机制(三大步骤)#

参考回答(面试口语化——字节高频题):

类加载分为三大步:加载 → 连接 → 初始化

① 加载(Loading)

  • 通过类的全限定名获取二进制字节流
  • 将字节流的方法区数据结构存储
  • 在内存中生成 java.lang.Class 对象

② 连接(Linking)

  • 验证:确保字节流符合 JVM 规范(文件格式、元数据、字节码、符号引用验证)
  • 准备:为类变量(static)分配内存并赋默认值(int=0, boolean=false, 引用=)
  • 解析:将常量池中的符号引用替换为直接引用

③ 初始化(Initialization)

  • 执行 <clinit>() 方法——收集所有类变量的赋值动作和静态代码块
  • 父类先初始化,子类后初始化

双亲委派模型

类加载器层次:Bootstrap → Extension → Application → 自定义
加载请求先委派给父加载器,父加载器能加载就不让子加载器加载

面试加分:打破双亲委派——自定义类加载器重写 loadClass() 而不是 findClass()


题9:MySQL undo log / redo log / binlog + 两阶段提交#

参考回答(面试口语化——字节经典组合考法):

三种日志的角色:

日志归属作用
undo logInnoDB 引擎事务回滚 + MVCC(多版本并发控制)
redo logInnoDB 引擎崩溃恢复——提交了的修改不丢(WAL:Write-Ahead Logging)
binlogMySQL Server 层主从复制 + 数据恢复

两阶段提交(保证 redo log 和 binlog 一致性):

事务提交时:
第一阶段(Prepare):
1. redo log 写入 Prepare 状态
2. binlog 写入(此时 binlog 完整记录了事务)
第二阶段(Commit):
3. redo log 写入 Commit 状态
4. 事务正式提交

为什么需要两阶段? → 防止 redo log 和 binlog 不一致。

  • 如果先写 redo log 再写 binlog,redo log 写完 binlog 没写完时 crash,恢复时用 redo log 恢复了事务但 binlog 没有,主从复制就丢了数据
  • 两阶段提交保证了两份日志的原子性

题10:并发:synchronized / ReentrantLock / CAS / AQS#

参考回答(面试口语化——字节/阿里高频八股):

synchronized(JVM 关键字)

  • JDK 1.6 后优化:偏向锁 → 轻量级锁 → 重量级锁(锁升级,单向不可逆)
  • 自动加锁解锁,发生异常自动释放
  • 比较重,但有 JVM 层面的优化

ReentrantLock(JDK 工具类)

  • 更灵活:支持公平锁/非公平锁、可中断、可限时 tryLock()
  • 可以有多个等待条件(Condition)
  • 必须手动加锁解锁(finally 中释放),不然会导致死锁

CAS(Compare-And-Swap,CPU 原子指令)

  • 原子操作:比较内存值和期望值,相等则更新为新值
  • 优点:无锁,性能好
  • 缺点:ABA 问题、自旋消耗 CPU、只能保证一个共享变量的原子性

AQS(AbstractQueuedSynchronizer,抽象队列同步器)

  • ReentrantLock、Semaphore、CountDownLatch 的基础
  • 核心:一个 volatile int state(同步状态)+ CLH 双向队列(等待线程)
  • 模板方法模式:子类实现 tryAcquire / tryRelease

面试加分追问:synchronized 和 ReentrantLock 性能对比?→ 低竞争下 synchronized 更优(有 JIT 优化),高竞争下 ReentrantLock 更优(可中断)。


题11:HTTP 1.0 / 2.0 / 3.0 区别#

参考回答(面试口语化——滴滴常问基础):

版本核心特点主要问题
HTTP/1.0每个请求新建 TCP 连接,用完就断开连接无法复用,效率极低
HTTP/1.1长连接(keep-alive)、管道化(pipelining)、分块传输、Host 头管道化有队头阻塞(HOL blocking)——前一个请求没返回,后面的不能发
HTTP/2.0多路复用(一个 TCP 连接发多个请求)、二进制帧、头部压缩(HPACK)、服务端推送仍有 TCP 级别的队头阻塞(丢包影响整个连接)
HTTP/3.0基于 QUIC(基于 UDP),解决队头阻塞、0-RTT 连接建立部署还不广泛,UDP 可能被防火墙拦截

面试加分:SSE 和 WebSocket 的对比是同一个体系的,可以关联起来讲。


题12:Python 多进程/多线程/协程区别#

参考回答(面试口语化——滴滴常问):

机制特点适用场景
多进程每个进程独立内存,开销大,通过 multiprocessing 实现CPU 密集型(计算密集型),利用多核
多线程共享内存,Python 有 GIL(全局解释器锁),同一时刻只有一个线程执行 Python 字节码IO 密集型(网络请求、文件读写),GIL 在 IO 等待时释放
协程用户态调度,单线程并发,通过 async/await 实现高并发 IO 场景(Web 服务器、爬虫)

常见问题:

  • Python 多线程是假的? → 对 CPU 密集型是假的(GIL 限制),对 IO 密集型是真的(IO 等待时释放 GIL)
  • 协程和线程的区别? → 协程是用户态切换(几十纳秒),线程是内核态切换(微秒级)。协程更轻量,但不能利用多核

题13:SSE 和 WebSocket 区别#

参考回答(面试口语化——FN/HQ公司高频题):

对比维度SSE(Server-Sent Events)WebSocket
方向单向:服务端 → 客户端双向:客户端 ↔ 服务端
协议基于 HTTP独立的 WebSocket 协议(ws://)
自动重连✅ 原生支持(EventSource 自动重连)❌ 需要自己实现
数据格式文本(通常是 JSON)文本+二进制
浏览器支持大部分浏览器支持所有现代浏览器支持
适用场景流式输出(LLM 逐字返回)、通知推送聊天、实时协作、游戏

在 Agent 开发中的应用:

  • SSE:LLM 流式输出——用户能看到 Agent”边想边说”的效果
  • WebSocket:Agent 间的实时通信、协作场景

面试追问:SSE 能传二进制吗?→ 原生不行(EventSource),但可以用 base64 编码后传。不过既然要传二进制,为什么不用 WebSocket?


三、🧠 算法/ML 基础 · 非训练岗也要知道(4 题)#


题14:注意力机制(QKV)#

参考回答(面试口语化——应用岗也要掌握基础):

注意力机制就是**“从大量信息中聚焦重要信息”**。

公式:Attention(Q, K, V) = softmax(QKᵀ / √dₖ) V

三个角色:

  • Q(Query,查询):当前要关注什么(比如当前词)
  • K(Key,键):所有候选信息的标识(比如每个词的索引)
  • V(Value,值):候选信息的实际内容(比如每个词的嵌入)

计算流程:

1. Q 和每个 K 做点积 → 得到相似度分数
2. 除以 √dₖ(缩放,防止点积方差过大导致 softmax 梯度太小)
3. softmax 归一化 → 得到注意力权重
4. 权重 × V → 加权求和得到 Attention 输出

面试加分:用直觉解释——“就像你在教室里找朋友(Q 是’你朋友的长相特征’),你跟每个人对比相似度(Q×K),最像的几个重点关注(softmax 权重),你把他们的特征综合起来形成最终印象(权重×V)“。


题15:位置编码(绝对/相对/RoPE)#

参考回答(面试口语化——理解 RoPE 为什么是主流):

为什么需要位置编码? → Transformer 的 Self-Attention 是置换不变的(不知道词的先后顺序),所以需要显式告诉模型”词的位置信息”。

三种方案:

① 绝对位置编码(Sinusoidal)

  • Transformer 原版,用 sin/cos 函数生成固定位置编码
  • 优点:无需学习,泛化到任意长度
  • 缺点:每个位置固定,不能表达相对位置关系

② 相对位置编码

  • 编码的是”两个位置之间的距离”而不是”某个位置的编号”
  • 例如 ML-Transformer、T5 的相对偏置
  • 优点:考虑了相对距离,泛化性好

③ RoPE(旋转位置编码,Rotary Position Embedding)——主流方案

  • LLaMA 系列、Mistral 等用的方案
  • 原理:在 Q 和 K 上做旋转变换,让内积结果包含位置信息
  • 本质:把”绝对位置”和”相对位置”统一了——内积结果只依赖相对位置
  • 优点:自然可外推(Extrapolation)、无需额外参数、与 attention 计算融合

面试加分:RoPE 是主流因为它在”绝对位置编码的简洁性”和”相对位置编码的表达力”之间取了平衡。


题16:过拟合 / 梯度消失#

参考回答(面试口语化——基础概念):

过拟合(Overfitting):

  • 模型在训练数据上表现很好,但在测试/新数据上表现很差
  • 原因:模型学习了训练数据的噪声(而不是真实规律),模型太复杂、数据太少
  • 解决方法:
    • 增加训练数据 / 数据增强
    • 减少模型复杂度(减少层数/参数)
    • 正则化(L1/L2)
    • Dropout
    • Early Stopping

梯度消失(Vanishing Gradient):

  • 反向传播时,靠近输入层的梯度趋近于0,导致这些层无法更新
  • 原因:激活函数选择不当(如 sigmoid 在两端梯度接近0)+ 深层网络连乘
  • 解决方法:
    • 用 ReLU 类激活函数(梯度=0或1,不会指数级缩小)
    • Batch Normalization(让激活值分布正常)
    • 残差连接(ResNet,让梯度可以有捷径通路)

题17:数据并行 / 模型并行#

参考回答(面试口语化——Agent实习面经常见):

数据并行:

  • 多个设备(GPU)复制同一份模型
  • 每个设备处理不同 batch 的数据
  • 前向传播独立计算,反向传播时梯度汇总(AllReduce)
  • 适用于:模型能放进单卡,但数据量太大需要加速

模型并行:

  • 一个模型太大放不进单卡,拆到多个设备上
  • 张量并行:某一层的计算拆分到多卡上(如注意力头拆分)
  • 流水线并行:不同层放不同设备上,数据像流水线一样经过各层
  • 适用于:模型超大(如 70B/175B 参数),单卡显存放不下

在 Agent 场景的应用:

  • Agent 通常不会用到这两种并行(因为模型调用走 API,不需要自己部署)
  • 但如果本地部署开源模型(如 Qwen-72B),就需要考虑——通常用张量并行部署

🎉 全部 78 道题参考回答已生成完毕!#

文档编号文档名称题目数状态
1参考回答_01_高频深度篇_A+与A级20题20 题
2参考回答_02_核心模块篇_B+级16题16 题
3参考回答_03_各厂特色题篇_公司特色与通用追问34 题
4参考回答_04_项目面试话术篇_决定Offer的关键叙事7 题
5参考回答_05_算法与CS基础篇_手撕代码与八股17 题
合计5 份文档94 题(含复用+拓展)全部完成

祝你面试顺利,offer 拿到手软!🚀

Share Article

If this article helped you, please share it with others!

🎯 参考回答 · 算法 / CS 基础篇(17 题 · C 级但不过不行)
https://estars-blog.pages.dev/posts/精华-参考回答_05_算法与cs基础篇_手撕代码与八股/
Author
Estars
Published at
2026-06-16
License
CC BY-NC-SA 4.0
Profile Image of the Author
Estars
这条路要走完,才能看到世界的终点,是海纳百川,还是星火燎原。
公告
欢迎来到我的博客!这是一则示例公告。
Music
Cover

Music

No playing

0:00 0:00
No lyrics available
Categories
Tags
Site Statistics
Posts
85
Categories
7
Tags
15
Total Words
218,958
Running Days
0 days
Last Activity
0 days ago

Table of Contents