news 2026/9/1 20:26:25

英语流利说秋招笔试题解析:从音素分割到高并发语音评测架构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
英语流利说秋招笔试题解析:从音素分割到高并发语音评测架构

从拿到英语流利说2019秋招技术类笔试题的那一刻起,我的第一反应是:这公司是真把业务揉进笔试里了。市面上大多数技术笔试都是教科书味的算法题,一套模板走天下,但流利说的卷子明显不同,字符串编辑距离、语音评测链路、弱网优化这些考点全都围着“AI+教育”这个核心场景转。如果你正准备投这类AI教育公司,或者对语音方向的技术栈感兴趣,这篇文章值得你花20分钟读完。我会从考察逻辑、典型算法题推导、系统设计思路、基础考点和准备策略五个方面,把这份卷子拆开揉碎讲一遍。

1. 这份笔试题的考察逻辑:为什么这么出题

1.1 技术栈与岗位画像

英语流利说的业务核心是“AI老师”,用户对着手机读一段英文,系统要实时打分、纠音、给出反馈。这决定了它的技术岗位画像不是纯粹的算法研究员,也不是只会CRUD的后端开发,而是“懂算法落地、懂高并发、懂音频处理”的复合型候选人。

笔试题目设计有两条隐含主线:一是考察你能否把经典算法应用到非经典场景,二是考察你是否有全局视野,能从一次用户录音请求出发,看到后端全链路。如果你只刷LeetCode,不关注业务场景,做这份卷子会明显觉得别扭。

1.2 题型分布与时间分配的博弈

我印象里整张卷子分为四块:选择题/填空题、算法编程题、系统设计题、计算机基础简答题。时间一共120分钟,题量不算少。这里有个容易被忽视的点:算法题占分比没有想象中高,系统设计和基础题才是拉开差距的地方。

我的建议是拿到卷子先用3分钟扫一遍全部题目,给每道题预估一个时间上限。算法题如果15分钟内想不出最优解,立刻写暴力解保分。千万别在一道DP题上死磕40分钟,导致后面的系统设计题只能写两行字。我当年就吃过这个亏,算法题写爽了,系统设计题草草收尾,最后面试时被追问得很难受。

2. 算法题实战复盘:从暴力解到最优解的完整推导

2.1 动态规划变形:音素序列分割问题

原题的大意是:一段语音被切分为N个音素片段,每个片段有个“流畅度得分”,由于音素边界检测存在误差,选取的片段之间至少需要间隔K个片段,求能获得的最大总得分。

这道题本质是“打家劫舍”的变体,但多了间隔K的限制。第一眼看到它,我脑子里跳出来的是DP,但状态转移方程需要仔细推。

定义dp[i]表示从前i个片段中能获得的最大总得分,那么对于第i个片段,有两种选择:

  • 不选第i个片段:dp[i] = dp[i-1]
  • 选第i个片段:dp[i] = dp[i-K-1] + score[i]

取两者的较大值。初始状态dp[0]=0,dp[1]=score[1](如果i-K-1小于0,说明前面没有可选片段,直接取score[i])。

def max_score(scores, K): n = len(scores) dp = [0] * (n + 1) for i in range(1, n + 1): # 不选当前片段 dp[i] = dp[i - 1] # 选当前片段,前一个可选位置是 i-K-1 prev = i - K - 1 if prev < 0: dp[i] = max(dp[i], scores[i - 1]) else: dp[i] = max(dp[i], dp[prev] + scores[i - 1]) return dp[n]

注意这里的边界处理:片段下标从1开始方便表示,scores[i-1]是第i个片段的得分。如果K=1,就是“不能选相邻片段”的经典打家劫舍;如果K=0,那就是“选或不选”的0/1背包了。

这道题还有个进阶问法:如果片段总数N达到10^6,O(N)的DP已经是最优,但如果你用Python的递归加备忘录,很可能会因为递归深度爆栈,所以必须用迭代写法。

2.2 字符串处理:带权重的音素编辑距离

第二道编程题让我印象深刻,因为它直接映射了语音评测里的“发音纠错”功能。题目给出一段标准音素序列和一段用户实际发音的音素序列,允许三种操作:插入、删除、替换,但每个操作的代价不同,比如替换的代价是2,插入和删除的代价都是1,求最小编辑代价。

经典编辑距离大家都刷过,但带权重之后,状态转移方程需要微调。dp[i][j]表示标准序列前i个音素和用户序列前j个音素的最小编辑代价:

  • 如果A[i] == B[j]:dp[i][j] = dp[i-1][j-1]
  • 否则:
    • 替换:dp[i-1][j-1] + cost_replace
    • 插入(在标准序列中插入一个音素):dp[i][j-1] + cost_insert
    • 删除(删除标准序列中的一个音素):dp[i-1][j] + cost_delete
def min_edit_cost(A, B, cost_insert=1, cost_delete=1, cost_replace=2): n, m = len(A), len(B) dp = [[0] * (m + 1) for _ in range(n + 1)] for i in range(n + 1): dp[i][0] = i * cost_delete for j in range(m + 1): dp[0][j] = j * cost_insert for i in range(1, n + 1): for j in range(1, m + 1): if A[i-1] == B[j-1]: dp[i][j] = dp[i-1][j-1] else: dp[i][j] = min( dp[i-1][j-1] + cost_replace, dp[i][j-1] + cost_insert, dp[i-1][j] + cost_delete ) return dp[n][m]

这里有个很实用的优化:如果cost_replace >= cost_insert + cost_delete,那么替换永远不划算,这时候可以用插入+删除组合代替替换。很多同学在笔试时忽略了这一点,导致结果偏大。

更深一层,这道题还能引导你思考:真实语音评测里,编辑距离只是最底层的对齐工具,实际还要结合发音得分、重音、连读等特征,但如果连编辑距离都写不对,后面的优化就无从谈起。

2.3 数据结构设计:支持区间统计的在线榜单

第三道算法题是纯数据结构设计:设计一个类,支持三个操作——添加一个用户分数、删除一个用户分数、查询分数在[low, high]区间内的用户数量。分数范围是0到10^9,操作次数是10^5。

第一个直觉是用有序数组或链表,但删除和查询都是O(N),肯定超时。第二个直觉是平衡二叉搜索树,但手写红黑树在笔试里不现实。正确解法是用树状数组(Fenwick Tree)做离散化。

把所有出现过的分数收集起来,排序去重,映射到1到M的连续下标,然后树状数组维护每个分数段的人数。查询区间[low, high]时,先二分找到low和high对应的离散化下标,再调用前缀和函数。

class FenwickTree: def __init__(self, n): self.n = n self.bit = [0] * (n + 1) def add(self, idx, delta): while idx <= self.n: self.bit[idx] += delta idx += idx & -idx def sum(self, idx): res = 0 while idx > 0: res += self.bit[idx] idx -= idx & -idx return res def range_sum(self, left, right): if left > right: return 0 return self.sum(right) - self.sum(left - 1)

这道题考察的不只是树状数组模板,还有离散化的意识。当时有同学直接用有序字典或者优先队列,删除操作复杂度就崩了。笔试时选择数据结构,一定要先分析每个操作的时间复杂度上限,再倒推合适的结构。

3. 系统设计题:语音评测服务的高并发架构

3.1 需求拆解:从录音上传到评分回传的全链路

系统设计题的大背景是:英语流利说的核心功能“跟读评测”,用户朗读一段英文,App录音上传,后端进行语音识别、发音评分、流利度分析,最后把分数和纠音建议推回App。请你设计一个支持百万级日活用户的后端架构。

这道题是典型的“大而全”设计题,关键不是堆组件,而是展示你如何拆解需求、识别瓶颈。完整链路可以拆成五个环节:客户端录音、文件上传、消息异步处理、AI推理打分、结果存储与推送。

很多人的第一个错误是只画了一个简单的“客户端-服务器-数据库”架构图,然后开始谈MySQL分库分表。但实际上,语音文件的上传和AI推理是最大的瓶颈。录音文件通常是几MB到几十MB,不能直接存MySQL,也不能用同步HTTP请求等AI推理完成后再返回,因为一次推理可能要几百毫秒到几秒,用户的请求早就超时了。

正确的拆解方式是:

  • 客户端录音结束后,先把音频文件上传到对象存储;
  • 上传成功后,服务器只返回一个“上传成功”的确认,并生成一个task_id;
  • 服务器把task_id和音频元信息写入消息队列;
  • 后端的AI推理Worker从队列里拉取任务,执行语音识别和评分;
  • 推理完成后,将结果写入结果存储,并通过长连接或推送通知把结果回传客户端。

这个链路的核心是“异步化”,把耗时操作从请求主路径中剥离出来。

3.2 存储选型:对象存储+缓存+分库分表

存储层面,至少需要三类存储:

  • 对象存储:存放原始音频文件和评测报告文件,按日期分目录,文件名用task_id或用户ID做哈希散列,避免单目录文件过多。
  • 缓存:用Redis缓存热点用户的最近评测结果,方便用户回看历史记录。key的设计建议是eval:result:{userId}:{taskId},过期时间设置5天。
  • 关系型数据库:存储用户基本信息、任务状态、成绩汇总。这里才用到分库分表,按userId哈希分16个库,再按时间按月分表。

我特意强调一下:不要在系统设计里把所有数据都塞进一个组件。很多候选人在笔试里写“用Redis存所有东西”,这是大忌。Redis适合做缓存和轻量级队列,不适合做持久化主存储,因为内存成本高、数据可靠性不足。

关于分库分表,有一个常见的面试追问:分表键怎么选?如果按userId分表,那么“查询某用户最近的评测记录”这个操作就非常快;但如果运营人员要按时间维度统计全平台的评测量,就需要扫描所有分表。这是典型的分布式系统trade-off,你要在卷面上明确说出你的取舍依据。

3.3 异步处理:消息队列削峰与结果回调

消息队列是这道题的灵魂。为什么不用同步HTTP调用AI服务?因为AI推理服务是CPU密集型,并发能力有限,而且语音评测有明显的波峰波谷——晚上8点到10点是使用高峰期,如果所有请求都直接打到AI服务,服务必定被打垮。引入消息队列后,生产者(上传服务)和消费者(AI Worker)之间解耦,队列天然具备削峰填谷的能力。

选择哪个消息队列?笔试题不会限定技术栈,你写Kafka、RocketMQ、RabbitMQ都可以,但要说清楚理由。我个人推荐写Kafka或RocketMQ,因为它们的吞吐量大、支持消费者组扩展。尤其是RocketMQ,支持延迟消息和事务消息,在评测结果回传场景下很好用。

还有一个关键细节:结果回调。AI推理完成后,怎么把结果推送到客户端?两种主流方案:

  • 客户端定时轮询:每秒请求一次“任务状态”接口,实现简单,但有延迟且浪费服务器资源;
  • WebSocket长连接:客户端建立长连接后,服务器主动推送结果,体验更好。

在笔试里,你应该选择WebSocket方案,并说明原因:移动端网络环境复杂,轮询在弱网下表现差,而且频繁轮询会产生大量无效请求。如果担心WebSocket连接断开,可以设计一个补偿机制——客户端在断线重连后,根据本地缓存的task_id主动查询一次结果。

4. 计算机基础题:藏在八股文里的真实考点

4.1 操作系统:进程线程与协程在AI推理中的取舍

基础题里有一道让我印象很深:在语音评测的AI推理服务中,为了提高并发能力,应该用多进程、多线程还是协程?为什么?

很多人看到这道题,直接写“用协程,因为协程轻量”,但这是不完整的。AI推理的底层通常依赖深度学习框架(如TensorFlow、PyTorch),这些框架的推理操作是CPU密集或GPU密集的,而且很多底层库是C++实现的,内部有自己的线程池。在Python里用协程,如果遇到CPU密集的计算,协程并不会自动让出控制权,反而可能因为GIL的存在导致其他线程阻塞。

合理的方案是“多进程 + 少量线程”。多进程可以充分利用多核CPU,每个进程内部署一个模型副本,进程间互不干扰。线程池用于处理I/O密集型操作,比如读取音频、HTTP请求等。协程更适合用在高并发I/O场景,比如网关层、代理层。

这道题考察的其实是“技术选型要结合场景”,而不是背概念。回答案例时要讲清楚:Python的GIL决定了纯计算任务用线程是伪并行;GPU推理通常受CUDA流和显存限制,也不能无限开线程。

4.2 网络:弱网环境下的传输优化

另一道网络题是:用户在移动网络环境下进行口语评测,如何保证录音文件上传的稳定性和成功率?

这道题可以从三个层面答:

  • 传输层:使用HTTP/2或QUIC协议,支持多路复用和更好的拥塞控制,减少TCP队头阻塞。
  • 应用层:分片上传,把录音文件切成2MB左右的块,每个分片独立上传、独立重试,最后服务端合并。断点续传也是这里的关键词。
  • 客户端策略:根据当前网络状态动态调整录音质量和采样率。比如Wi-Fi下用48kHz采样,4G网络用16kHz采样,2G/3G网络降级为8kHz,优先保证上传成功率,评分模型可以兼容不同采样率。

这里有一个我实际踩过的坑:分片上传时,如果每个分片都创建一个独立的HTTP连接,握手开销会很大。正确做法是复用连接,并且用并发数为3~5的线程池控制上传速度,避免把用户的上行带宽全部占满,影响其他业务。

4.3 数据库:索引失效场景与慢查询排查

数据库题考察了一个很常见的场景:评测记录表有userId、created_at、score三个字段,查询语句是SELECT * FROM evaluation WHERE user_id = ? AND created_at BETWEEN ? AND ? ORDER BY created_at DESC LIMIT 10,请说明如何设计索引,并列举至少两种索引失效的场景。

正确做法是建立联合索引(user_id, created_at),顺序不能颠倒。因为查询条件中user_id是等值匹配,created_at是范围匹配,联合索引可以同时过滤两个条件,避免回表。

索引失效的常见场景要记住三条:

  1. 对索引列使用函数,比如WHERE DATE(created_at) = '2024-01-01',会导致索引失效,应该改为created_at >= ? AND created_at < ?
  2. 隐式类型转换,比如user_id字段是varchar,查询时传入数字,MySQL会触发隐式转换,索引失效;
  3. 左模糊查询,LIKE '%abc'无法使用索引,而LIKE 'abc%'可以用索引。

我在笔试时额外提了一条:如果查询量巨大,可以考虑用覆盖索引,把score字段也放进索引里,这样可以直接从索引中返回数据,避免回表IO。这些细节虽然是小点,但能体现你真的处理过慢查询问题。

5. 准备建议与踩坑提醒:从我实际笔试经历中提炼

5.1 时间分配策略:先保分还是先攻坚

整张卷子做完,我最深的体会是:笔试不是竞赛,是“在有限时间内展示最大价值”。如果你在一道题上卡了20分钟,果断跳过,把能拿的分先拿到。我当时的策略是:填空选择题限时30分钟,算法题每道最多35分钟,系统设计题留40分钟,基础题最后20分钟收尾。

系统设计题很容易被低估,很多同学先做算法题,最后只剩15分钟写设计题,结果只画了架构图,没有说明存储选型和消息队列的细节,分数直接被砍掉一大截。如果你时间紧张,宁可写一个完整的“简化版”系统设计——一条清晰的主链路加关键组件——也不要东写一句西写一句。

5.2 代码规范细节:面试官真正在看什么

编程题不光看答案对不对,还看代码风格。笔试平台一般支持选择题和代码题,代码题如果AC了,面试官会看你的解题思路;如果没有AC,但代码结构清晰、注释到位,也能获得一些同情分。

有几个细节我能提醒就提醒一下:

  • 变量命名要有意义,不要用abc这类无意义名称;
  • 边界条件一定要处理,比如数组为空、K为0、分数范围越界;
  • 复杂度分析要写在注释里或提交前最后一段文字中,让面试官快速了解你的思路。

我当时在编辑距离那道题里,虽然代码AC了,但没写复杂度分析,面试时被追问才补充。其实笔试时在代码注释里写上“O(n*m)时间和O(m)空间(滚动数组优化)”是很加分的。

5.3 复盘方法:笔试结束后的二次价值

笔试结束后,别急着丢到一边。我自己会把每道题的解题思路重新整理一遍,尤其是那些没做出来的题,花时间去查最优解、写一遍完整代码。这样做的好处是,面试时如果问到类似问题,你能立刻调出清晰的思路;而且流利说这种公司,面试题往往和笔试题高度关联,笔试里出现的系统设计题,面试里大概率会继续深挖。

我当年笔试后的复盘笔记里,专门把“音素序列分割”和“编辑距离”两道题归类为“业务算法题”,总结出这类题目的通用解法框架:先抽象业务场景的数据结构,再套经典算法模板。这个框架后来对我在其他AI公司的面试也很管用。

最后再分享一个真实感受:流利说这套笔试题,与其说是在筛选“刷题机器”,不如说是在筛选“能用技术解决实际问题的人”。如果你备考时只是埋头刷LeetCode,可以理解业务场景的技术需求可能不够;但如果你也花时间研究过语音评测、移动端弱网优化、异步消息架构这些实际工程问题,答起来会顺手很多。准备时把视野放宽一点,多做跨知识域的串联,比单纯刷题更值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 20:22:59

美团校招测试简答题复盘:从测试思维到自动化框架的备考指南

去年秋招我在准备软件测试岗位时&#xff0c;把网上流传的“美团2023校招测试-简答题(第1/2批)”翻来覆去看了好几遍。第一眼的感觉是&#xff1a;这些题比算法题友好多了&#xff0c;至少能看懂题目在问什么。但真正动手写答案的时候才发现&#xff0c;简答题才是筛人的重头戏…

作者头像 李华
网站建设 2026/9/1 20:20:09

iHandy 2019校招技术笔试全解析:考点、答题思路与备考策略

每年秋招季&#xff0c;各类工具类App公司的技术笔试题总会被翻出来反复研究&#xff0c;iHandy的题就是其中一个绕不开的样本。这家公司做移动工具类产品出身&#xff0c;用户量级大、产品线多&#xff0c;所以笔试题目并不是单纯的“刷题筛人”&#xff0c;而是既考基本功&am…

作者头像 李华
网站建设 2026/9/1 20:19:35

用友Java笔试真题解析:从String到JVM与Spring核心考点

1. 这套题背后的出题逻辑&#xff1a;用友秋招Java笔试到底在考什么前两天整理硬盘&#xff0c;翻出了自己当年秋招时存的一份用友2018秋招Java笔试题&#xff0c;第六套。重看一遍感触挺深——用友这类传统软件大厂的笔试风格&#xff0c;和互联网大厂的出题思路确实不太一样。…

作者头像 李华
网站建设 2026/9/1 20:18:29

MySQL按中文排序:ORDER BY遇到中文乱序怎么办?5种方案

做后台开发的同学应该都碰到过这个场景&#xff1a;页面上有个下拉列表或者表格&#xff0c;需要按中文姓名、城市名排序显示&#xff0c;结果一查出来&#xff0c;张三排到李四前面还是后面完全看运气&#xff0c;搞得产品经理天天追着你问"这个排序怎么是乱的"。My…

作者头像 李华
网站建设 2026/9/1 20:11:41

Sentinel实战:微服务限流、熔断与降级的核心原理与落地

先说结论&#xff1a;Sentinel 是面向微服务、分布式系统的流量治理组件&#xff0c;核心就三件事&#xff1a;限流、熔断降级、系统保护。微服务里真正让人头疼的不是功能开发&#xff0c;而是流量一上来、下游一慢、某个接口一抖动&#xff0c;整个链路跟着挂。很多人把“限流…

作者头像 李华
网站建设 2026/9/1 20:10:08

深入理解POSIX:编写跨平台Shell脚本的兼容性指南

各位读者朋友&#xff0c;大家好。之前在实际开发中&#xff0c;经常遇到这样一个场景&#xff1a;同一份 Shell 脚本&#xff0c;在一台 Ubuntu 服务器上运行得好好的&#xff0c;换到本机 macOS 终端里就报错&#xff0c;或者输出结果对不上。网上搜了一圈&#xff0c;答案零…

作者头像 李华