Skip to content

Commit 0c76547

Browse files
committed
feat: 大厂面经
1 parent 4e2b389 commit 0c76547

7 files changed

Lines changed: 733 additions & 0 deletions
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
---
2+
title: "m佬PDD服务端开发实习"
3+
---
4+
5+
# m佬 PDD 服务端开发实习
6+
7+
岗位:服务端开发实习
8+
9+
## 面试流程
10+
11+
### 一面
12+
13+
1. 项目细节追问。
14+
2. 手撕 UDP 黏包问题相关题。
15+
3. 简单模拟题。
16+
17+
### 二面
18+
19+
1. 手写 HashMap,实现方式是开放寻址。
20+
2. 围绕 HashMap 底层聊了较多。
21+
3. 项目细节追问较多。
22+
23+
### 三面
24+
25+
1. 面试官对开源经历比较感兴趣,围绕开源项目聊了很多。
26+
2. 手撕经典前缀表达式求值。
27+
28+
### HR 面
29+
30+
1. 聊研究生阶段做项目的一些经历。
31+
2. 结果:HR 面后流程结束。
32+
33+
## 参考答案(AI 生成)
34+
35+
> 以下答案由 AI 生成,仅供面试复盘参考。
36+
37+
### 1. UDP 黏包问题怎么回答?
38+
39+
答:UDP 按报文交付,应用层一次 `recvfrom` 读取的是一个 datagram。面试里如果提到 UDP 黏包,可以先说明 UDP 保留报文边界,重点风险在于报文过大导致 IP 分片、接收缓冲区过小导致截断、网络丢包乱序,以及应用层把多个逻辑消息塞进一个 UDP 包后需要自行拆分。
40+
41+
工程处理思路是设计应用层协议。比如每个逻辑消息使用 `length + payload`,或者使用固定长度头部,头部里放 magic、version、seq、len、checksum。接收后先校验头部和长度,再按协议拆出多个逻辑消息。
42+
43+
```go
44+
func splitFrames(buf []byte) ([][]byte, error) {
45+
frames := make([][]byte, 0)
46+
for len(buf) > 0 {
47+
if len(buf) < 4 {
48+
return nil, fmt.Errorf("short header")
49+
}
50+
n := int(binary.BigEndian.Uint32(buf[:4]))
51+
if n < 0 || len(buf[4:]) < n {
52+
return nil, fmt.Errorf("invalid frame length")
53+
}
54+
frames = append(frames, buf[4:4+n])
55+
buf = buf[4+n:]
56+
}
57+
return frames, nil
58+
}
59+
```
60+
61+
### 2. 开放寻址 HashMap 怎么实现?
62+
63+
答:开放寻址把所有元素都放在数组里,冲突时按探测序列寻找下一个可用槽位,常见探测方式有线性探测、二次探测和双重哈希。每个槽位通常有三种状态:empty、used、deleted。查找时从 hash 位置开始探测,遇到目标 key 返回,遇到 empty 说明 key 缺席,遇到 deleted 继续探测。
64+
65+
扩容条件通常基于负载因子,比如元素数量超过容量的 0.7 时扩容到两倍,再把旧元素重新插入新数组。删除时使用 deleted 标记,保留探测链路,后续可以在扩容时清理 tombstone。
66+
67+
```go
68+
type entry struct {
69+
key string
70+
val int
71+
state uint8 // 0 empty, 1 used, 2 deleted
72+
}
73+
74+
type HashMap struct {
75+
data []entry
76+
size int
77+
}
78+
79+
func NewHashMap(capacity int) *HashMap {
80+
if capacity < 8 {
81+
capacity = 8
82+
}
83+
return &HashMap{data: make([]entry, capacity)}
84+
}
85+
86+
func (m *HashMap) Put(key string, val int) {
87+
if float64(m.size+1)/float64(len(m.data)) > 0.7 {
88+
m.resize(len(m.data) * 2)
89+
}
90+
m.putNoResize(key, val)
91+
}
92+
93+
func (m *HashMap) Get(key string) (int, bool) {
94+
idx := int(hashString(key) % uint64(len(m.data)))
95+
for i := 0; i < len(m.data); i++ {
96+
p := (idx + i) % len(m.data)
97+
e := m.data[p]
98+
if e.state == 0 {
99+
return 0, false
100+
}
101+
if e.state == 1 && e.key == key {
102+
return e.val, true
103+
}
104+
}
105+
return 0, false
106+
}
107+
108+
func (m *HashMap) Delete(key string) bool {
109+
idx := int(hashString(key) % uint64(len(m.data)))
110+
for i := 0; i < len(m.data); i++ {
111+
p := (idx + i) % len(m.data)
112+
e := m.data[p]
113+
if e.state == 0 {
114+
return false
115+
}
116+
if e.state == 1 && e.key == key {
117+
m.data[p].state = 2
118+
m.size--
119+
return true
120+
}
121+
}
122+
return false
123+
}
124+
125+
func (m *HashMap) putNoResize(key string, val int) {
126+
idx := int(hashString(key) % uint64(len(m.data)))
127+
firstDeleted := -1
128+
129+
for i := 0; i < len(m.data); i++ {
130+
p := (idx + i) % len(m.data)
131+
e := m.data[p]
132+
if e.state == 1 && e.key == key {
133+
m.data[p].val = val
134+
return
135+
}
136+
if e.state == 2 && firstDeleted == -1 {
137+
firstDeleted = p
138+
continue
139+
}
140+
if e.state == 0 {
141+
if firstDeleted != -1 {
142+
p = firstDeleted
143+
}
144+
m.data[p] = entry{key: key, val: val, state: 1}
145+
m.size++
146+
return
147+
}
148+
}
149+
}
150+
151+
func (m *HashMap) resize(capacity int) {
152+
old := m.data
153+
m.data = make([]entry, capacity)
154+
m.size = 0
155+
for _, e := range old {
156+
if e.state == 1 {
157+
m.putNoResize(e.key, e.val)
158+
}
159+
}
160+
}
161+
162+
func hashString(s string) uint64 {
163+
var h uint64 = 1469598103934665603
164+
for i := 0; i < len(s); i++ {
165+
h ^= uint64(s[i])
166+
h *= 1099511628211
167+
}
168+
return h
169+
}
170+
```
171+
172+
### 3. HashMap 底层常见追问
173+
174+
答:开放寻址的核心问题是冲突处理、删除标记、负载因子和扩容。负载因子过高会导致探测长度变长,查询和插入退化;删除留下 tombstone 会影响探测效率,需要在扩容或重建时清理。和链地址法相比,开放寻址局部性更好,数组连续存储更利于缓存,但对负载因子更敏感。
175+
176+
### 4. 前缀表达式求值
177+
178+
答:前缀表达式可以从右向左扫描。遇到数字就入栈,遇到运算符就弹出两个操作数计算,再把结果压回栈。扫描结束后栈顶就是结果。
179+
180+
```go
181+
func evalPrefix(tokens []string) (int, error) {
182+
stack := make([]int, 0)
183+
for i := len(tokens) - 1; i >= 0; i-- {
184+
t := tokens[i]
185+
switch t {
186+
case "+", "-", "*", "/":
187+
if len(stack) < 2 {
188+
return 0, fmt.Errorf("invalid expression")
189+
}
190+
a := stack[len(stack)-1]
191+
b := stack[len(stack)-2]
192+
stack = stack[:len(stack)-2]
193+
194+
var v int
195+
switch t {
196+
case "+":
197+
v = a + b
198+
case "-":
199+
v = a - b
200+
case "*":
201+
v = a * b
202+
case "/":
203+
if b == 0 {
204+
return 0, fmt.Errorf("division by zero")
205+
}
206+
v = a / b
207+
}
208+
stack = append(stack, v)
209+
default:
210+
v, err := strconv.Atoi(t)
211+
if err != nil {
212+
return 0, err
213+
}
214+
stack = append(stack, v)
215+
}
216+
}
217+
if len(stack) != 1 {
218+
return 0, fmt.Errorf("invalid expression")
219+
}
220+
return stack[0], nil
221+
}
222+
```
223+
224+
### 5. 开源经历怎么讲?
225+
226+
答:开源经历可以按“项目背景、参与动机、贡献内容、协作方式、工程收获”来讲。重点讲清楚你解决了什么问题、读了哪些模块、怎么和 maintainer 沟通、PR 怎么设计和验证、最终合入后带来什么效果。技术之外,也可以补充 issue 拆解、review 反馈处理、文档补充和社区协作经验。
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
title: "m佬淘天AI应用开发一面"
3+
---
4+
5+
# m佬淘天 AI 应用开发一面
6+
7+
岗位:AI 应用开发
8+
9+
## 面试流程简述
10+
11+
面试内容集中在项目经历和 AI coding 认知,整体更像围绕工程实践展开的技术探讨。面试官重点关注候选人如何端到端推进 PR、如何使用 AI 工具、如何看待未来研发流程,以及如何治理 AI coding 带来的语义偏移和上下文管理问题。
12+
13+
进展:已约二面。
14+
15+
## 问题记录
16+
17+
1. 拿一个 PR 讲,怎么端到端完成?
18+
2. 日常 AI coding workflow 是什么?
19+
3. AI 工具能力继续增强后,流程还能优化哪里?
20+
4. 怎么理解“语义偏移”?如何用 CI 做回收?
21+
5. 文档、代码、记忆 / 事实层如何分层?
22+
23+
## 参考答案(AI 生成)
24+
25+
> 以下答案由 AI 生成,仅供面试复盘参考。
26+
27+
### 1. 拿一个 PR 讲,怎么端到端完成?
28+
29+
答:可以按“需求理解、方案设计、实现、验证、评审、上线、回看”来讲。先明确 PR 要解决的用户问题、影响范围和验收标准,再读相关模块和历史 PR,确定改动边界;实现阶段保持小步提交,补单测、集成测试和必要的日志指标;提交前跑本地检查和关键链路验证;review 阶段重点解释设计取舍、风险和回滚方式;合并后观察线上指标和用户反馈。
30+
31+
### 2. 日常 AI coding workflow 是什么?
32+
33+
答:常见流程是先让 AI 帮忙读代码和梳理调用链,再让 AI 生成方案草稿和测试点,随后人工确定实现边界。编码阶段可以让 AI 承担样板代码、局部函数、测试用例和文档更新;验证阶段让 AI 根据 diff 找风险点,再配合单测、lint、类型检查和本地运行确认质量。核心是把 AI 放在“加速理解、生成候选方案、补齐测试和辅助 review”的位置上。
34+
35+
### 3. AI 工具能力增强后,流程还能优化哪里?
36+
37+
答:优化空间主要在自动化上下文收集、PR 级计划生成、测试自动补齐和上线后反馈回流。更理想的流程是 AI 能自动读取需求、关联历史代码、生成小步执行计划、产出 diff、补充测试、解释风险,并把线上指标、用户反馈和 review 意见沉淀回项目知识库。这样研发流程会从“人手动拼上下文”演进到“系统持续维护工程上下文”。
38+
39+
### 4. 怎么理解“语义偏移”?如何用 CI 做回收?
40+
41+
答:语义偏移指代码实现、测试、文档和需求意图在多轮修改后逐渐偏离,表面上能编译通过,实际业务语义已经漂移。CI 可以从多个层次做回收:单测覆盖函数语义,集成测试覆盖模块协作,契约测试覆盖 API 行为,快照测试覆盖结构化输出,端到端测试覆盖关键用户路径,静态扫描覆盖安全和规范。对于 AI coding,还可以在 CI 中加入 spec check、golden case、回归样例集和变更影响分析,让每个 PR 都回到原始需求和关键约束上。
42+
43+
### 5. 文档、代码、记忆 / 事实层如何分层?
44+
45+
答:可以分成三层。文档层承载相对稳定的设计意图、业务规则、接口契约和操作手册;代码层承载真实可执行逻辑、测试和配置;记忆 / 事实层承载项目当前状态、历史决策、用户偏好、线上现象和团队约定。使用 AI 时,文档层提供背景,代码层提供事实校验,记忆层提供个性化和连续性;检索和注入上下文时要带来源、时间、权限和置信度,保证模型拿到的是当前任务最相关的信息。
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
title: "m佬淘天AI应用开发二面"
3+
---
4+
5+
# m佬淘天 AI 应用开发二面
6+
7+
岗位:AI 应用开发
8+
9+
## 面试流程简述
10+
11+
二面整体仍然偏项目深聊和 AI 工程认知探讨,面试官会沿着一面的项目和观点继续往下追问。问题更关注候选人对 RAG、代码智能体、上下文压缩、工程安全和未来工具形态的判断。
12+
13+
## 问题记录
14+
15+
1. 聊到 RAG 时,怎么看待现在的 RAG?
16+
2. Karpathy 之前提到过用 Obsidian 做知识库,用 Markdown 和代码片段存储,检索已经能覆盖很多场景;包括 page index 这类方案也很实用。向量数据库在 RAG 中后续还有哪些需求?
17+
3. 聊到 Claude Code 的代码泄露和 harness engineer,怎么看待代码智能体的安全边界?
18+
4. 怎么看待 Claude Code 的上下文压缩方式?
19+
20+
## 参考答案(AI 生成)
21+
22+
> 以下答案由 AI 生成,仅供面试复盘参考。
23+
24+
### 1. 怎么看待现在的 RAG?
25+
26+
答:RAG 的核心价值是把外部知识以可检索、可引用、可更新的方式接入模型。现在的 RAG 已经从“向量召回 + 拼 prompt”演进成一套工程系统,包括文档治理、分块、索引、混合检索、rerank、权限过滤、上下文压缩、引用溯源和效果评测。真正影响效果的往往是数据质量、召回可控性、上下文组织和评测闭环。
27+
28+
### 2. 向量数据库在 RAG 中后续还有哪些需求?
29+
30+
答:向量数据库的价值取决于知识规模、查询复杂度和工程约束。小规模代码库、个人知识库、Markdown 文档和结构化代码片段,关键词检索、文件索引、AST/符号索引、page index 已经很强;企业级知识库、多语言语义查询、自然语言模糊表达、跨文档关联、海量增量索引、权限过滤和高并发检索,会继续需要向量索引和向量数据库。
31+
32+
更合理的判断是:RAG 会走向混合检索。代码类场景依赖符号索引、依赖图、全文检索和结构化检索;知识问答场景依赖 BM25、向量召回、rerank 和元数据过滤;Agent 场景还需要按任务动态选择工具、文档和历史记忆。向量数据库会成为检索栈的一层,而 RAG 的能力上限来自整体检索与上下文工程。
33+
34+
### 3. 怎么看待代码智能体的安全边界?
35+
36+
答:代码智能体有仓库读取、文件修改、命令执行、网络访问和凭证接触能力,权限边界要按真实工程工具来设计。安全策略可以分成四层:权限最小化、敏感信息隔离、执行审计、变更验证。比如限制可读目录和命令白名单,屏蔽 token、密钥和生产配置,对每次工具调用留审计日志,所有代码变更经过测试、review 和 CI。
37+
38+
聊到 Claude Code 代码泄露这类事件时,可以重点回答工程启示:把 AI coding 当成具备操作权限的工程系统管理,明确数据边界、日志边界、模型调用边界和插件边界。harness engineer 的价值也在这里,通过自动化评测、沙箱、回归集和安全用例持续验证智能体的行为。
39+
40+
### 4. 怎么看待 Claude Code 的上下文压缩方式?
41+
42+
答:上下文压缩的目标是在有限窗口里保留任务目标、关键约束、代码事实、已做决策和下一步计划。它能提升长任务连续性,也会带来信息损耗、事实漂移和约束丢失风险。压缩质量取决于摘要结构、来源引用、时间顺序和可恢复能力。
43+
44+
更稳的做法是把压缩结果分层:任务目标、用户约束、仓库事实、修改记录、未解决问题、验证结果。压缩后的摘要要保留文件路径、函数名、命令结果和关键决策来源;遇到高风险修改时,智能体应该回读源文件和测试结果,再继续执行。长期看,上下文压缩会和检索、记忆、trace、CI 结合,形成可追踪的工程上下文系统。

0 commit comments

Comments
 (0)