2018年秋招季,我投了迅雷的AI工程师岗位。当时在线笔试用的是第三方评测平台,限时90分钟,分选择题、简答题和两道编程题。说实话,那年头AI岗的笔试还没有现在这么“卷”,但迅雷的卷子考察面挺综合的,既考机器学习理论,也考代码功底,还会结合业务问你工程落地的问题。这篇文章是我结合当时的回忆和同届同学交流整理的考点复盘,列一些典型题型的思路分析和易错点,给后来准备算法岗笔试的同学做个参考。
1. 笔试整体结构与考察思路拆解
1.1 迅雷AI岗位的考察定位
迅雷这家公司,大家熟悉的是下载加速和流媒体,但AI团队在2018年前后主要做的事情是视频内容理解、画质增强、智能推荐、用户画像,以及分布式训练平台的建设。所以笔试的考察点不会纯考学术模型,而是偏向“能落地”的算法和工程能力。
我当时拿到的A卷分三块:
- 选择题(约20道):覆盖机器学习基础、深度学习基础、概率统计、数据结构。
- 简答题(2-3道):一般涉及模型推导或场景设计,比如“LR为什么要用交叉熵而不是MSE”“如何在短视频场景下做去重”。
- 编程题(2道):一道偏数据结构和算法,一道偏机器学习实现。
从结构能看出来,迅雷关注的不只是你会不会调包,还看你有没有数学功底和代码实现能力。这一点和很多大厂算法岗笔试一致:面试官默认你了解那些常用模型,笔试主要筛掉“只会调用、不懂原理”的人。
1.2 考察维度与权重参考
我根据后续面试过程中侧面了解到的信息,结合笔试内容,大致拆出这样的权重:
| 考察维度 | 典型内容 | 预估占比 |
|---|---|---|
| 机器学习理论 | LR、SVM、决策树、集成学习、正则化、评估指标 | 30% |
| 深度学习基础 | CNN、RNN、激活函数、反向传播、过拟合 | 20% |
| 概率统计与线性代数 | 贝叶斯、最大似然、矩阵运算、特征值 | 15% |
| 数据结构与算法 | 链表、树、DP、字符串、排序 | 20% |
| 工程与业务场景 | 特征工程、模型上线、业务案例分析 | 15% |
你发现没有:数据结构与算法依然占了五分之一。哪怕是AI岗,编程基本功还是硬门槛。很多同学花大量时间死磕模型细节,结果在第一道编程题上卡住,反而丢了最基础的分数,这一点特别可惜。
2. 机器学习基础高频考点解析
2.1 正则化与过拟合:L1和L2的本质区别
这类题目几乎是每年笔试必考的。选择题常见考法有两种:一是给你四个选项,问哪个手段不能防止过拟合(答案经常是“增加训练轮数”或“减少训练数据”);二是问L1和L2正则化在解空间上的区别。
L1正则化(Lasso)之所以能让参数变稀疏,是因为它在参数空间中对应一个菱形约束区域,最优解更容易落在坐标轴上,从而把某些特征的权重压成0。L2正则化(Ridge)对应圆形约束区域,能压缩参数大小,但不至于让参数变成严格的0。
如果简答题让你“从贝叶斯视角解释L1和L2”,你就得说出来:L1等价于给参数加了拉普拉斯先验,L2等价于加了高斯先验。拉普拉斯分布在0处概率密度最高,所以MAP估计更容易得到稀疏解。
我当年写这道题的时候,额外加了一句“在特征维度极高但样本量有限的场景下,L1可以自动做特征选择,对工业界特征稀疏的场景非常实用”。这种落脚到业务的补充,比干巴巴列公式更能拿分。
2.2 模型评估:AUC、PR曲线、F1的适用场景
选择题经常问的是“正负样本极度不平衡时,用哪个指标更好”。答案一般是PR曲线或F1,而不是准确率。因为准确率在正样本只占1%的时候,全部预测为负也能达到99%的准确率,完全失去参考价值。
AUC也有个特点是正负样本比例变化时基本保持稳定,所以像CTR预估这种场景,业内依然常用AUC。但如果关注的是“在有限的精密度下尽量召回更多的正样本”,PR曲线更直观。
有一道选择题我记得很清楚:给了混淆矩阵里的TP、FP、FN、TN数值,让你算Precision和Recall。这种题不需要动脑子,但很多人会在分母上栽跟头——Precision分母是TP+FP,Recall分母是TP+FN,千万别搞反。
2.3 经典模型对比:LR、SVM、GBDT
简答题常出“LR和SVM的区别”。答题要抓住几个关键维度:
- 损失函数不同:LR是交叉熵(对数似然损失),SVM是合页损失。
- 目标不同:LR建模的是后验概率,SVM找的是最大间隔超平面。
- 对异常值敏感度:LR对全部样本都敏感,SVM只受支持向量影响,因此SVM对远离决策边界的异常点更鲁棒。
- 处理非线性:LR需要手工做特征交叉核变换,SVM可以直接用核函数。
2018年前后,GBDT在工业界特别火,所以笔试也常考“GBDT和随机森林的区别”。一个核心点在两者都是树模型集成,但随机森林是Bagging,每棵树并行训练,降低方差;GBDT是Boosting,每棵树拟合前面残差,降低偏差。随机森林对异常值更鲁棒,GBDT对异常值敏感,容易过拟合。
2.4 特征工程与数据处理
有一道简答题是“给你一批用户点击日志,怎么构造特征”。这题没有唯一答案,考察的是你平时做特征工程的思路。我当时写的框架是:
- 统计特征:每个用户点击次数、点击率、平均停留时长。
- 序列特征:点击过的内容ID序列,用Embedding或统计频次表示。
- 时间特征:最近一次点击距现在的时间间隔、点击时间段分布。
- 交叉特征:用户类别与内容类别的组合,或用户活跃度分段与内容时长的组合。
回答这种题的核心是展示思路,别只给一两个特征就停了。面试官想看你有没有体系化的思考习惯。
3. 深度学习与算法题考察方向
3.1 CNN基础:卷积计算与感受野
2018年笔试题里,深度学习权重不低。最基础的考法是给一个输入尺寸、卷积核大小、步长和padding,让你算输出尺寸。公式是:
输出尺寸 = floor((输入尺寸 - 卷积核大小 + 2 × padding) / stride) + 1
如果输入是32×32,卷积核是3×3,stride=1,padding=0,那输出就是30×30。看似简单,但很多人会忘了stride向下取整的细节。
还有个高频概念“感受野”。简答题会问“两层3×3卷积的感受野等于一层多大卷积核的感受野”。答案是5×5,但参数数量更少、非线性更强。计算感受野有递推公式:
RF_{l} = RF_{l-1} + (kernel_size - 1) × stride_{l}
这个公式在选择题里换几个数字就能考,你要记住的是:stride大于1时感受野会加速增长,而不是简单叠加。
3.2 激活函数的选择与梯度消失
选择题常考“为什么ReLU比sigmoid好”。几个要点:
- sigmoid在两端梯度趋近于0,容易造成梯度消失。
- sigmoid输出均值不为0,会使得深层网络收敛变慢。
- ReLU在正区间梯度恒为1,缓解梯度消失,计算也简单。
- ReLU有个问题是负区间梯度为0,导致神经元“死亡”,所以后面才有LeakyReLU、PReLU等变体。
当年有一道题考的是“sigmoid函数的导数最大值是多少”。如果对sigmoid函数表达式熟悉,会求导,能得出最大值是0.25。这个数很关键,因为多层sigmoid堆叠时,每层梯度都要乘以一个小于1的数,层数一深,梯度基本就消失了。
3.3 反向传播与Softmax
有一道编程题我印象很深,是让你实现Softmax的前向和反向。题目不复杂,但把“类和对象”“矩阵运算”和“梯度推导”串在一起,纯靠背代码容易写崩。
Softmax前向是:
exp(x_i) / sum_j exp(x_j)
实现的坑在于数值稳定性。当x_i很大的时候,exp(x_i)会溢出,所以要先减去最大值:exp(x_i - max_x),这样分子分母同时缩放了相同倍数,结果不变,但数值稳定。
反向传播的推导是多数人的死穴。其实核心就一句话:Softmax和交叉熵搭配时,梯度是p - y,其中p是预测概率,y是one-hot标签。但如果单独实现Softmax反向,就得推导Jacobian矩阵,对角项是p_i(1-p_i),非对角项是-p_i p_j。笔试时我把这个写成了公式附在代码注释里,后来复盘觉得这种“代码+推导”的做法对拿分蛮有优势。
3.4 RNN与LSTM的考察
选择题常见考法是问“LSTM解决了RNN的什么问题”。标准答案是梯度消失和长期依赖。展开说就是LSTM通过输入门、遗忘门、输出门控制信息的保留和遗忘,让梯度能通过“细胞状态”这条高速公路传得更远。
当年还考了一道“RNN能不能并行训练”的判断题。答案是很难并行,因为时间步之间存在顺序依赖。Transformer是2017年提的,笔试时不算主流,但如果你当时能提一句“用注意力机制替代循环结构,可以并行计算”,妥妥的加分项。
4. 编程题实战:两道经典题目拆解
4.1 题目一:TopK问题,海量数据找最大K个数
这题当年是必考的经典题型,可能以“求一个数组里最大K个元素”的形式出现。它考察的不是你会不会排序,而是对复杂度敏感不敏感。
- 暴力排序:O(n log n)。
- 小顶堆维护K个元素:O(n log K)。
- 基于快速排序思想的partition:平均O(n)。
- 如果数据在磁盘上放不下,需要分布式或外排序。
我当时写的是小顶堆版本。用Python的话,标准库heapq搞定:
import heapq def top_k(nums, k): return heapq.nlargest(k, nums)但笔试一般不允许直接调nlargest,因为考察点就是你自己能不能实现堆化过程。我手写了一个堆的调整函数:
def shift_down(heap, root, size): while 2 * root + 1 < size: child = 2 * root + 1 if child + 1 < size and heap[child + 1] < heap[child]: child += 1 if heap[root] <= heap[child]: break heap[root], heap[child] = heap[child], heap[root] root = child def top_k(nums, k): heap = nums[:k] # 建小顶堆 for i in range(k // 2 - 1, -1, -1): shift_down(heap, i, k) for x in nums[k:]: if x > heap[0]: heap[0] = x shift_down(heap, 0, k) return heap这里最容易被忽视的是边界情况:k等于数组长度时直接返回原数组排序,k等于0时返回空数组。在线笔试平台不会给你的代码做人性化处理,只要有一个边界没覆盖到,运行时直接报错,那一整题就废了。
4.2 题目二:实现K-Means的一轮迭代
这道题放到现在可能算常规,但在2018年看到的时候我还愣了一下,因为平时刷LeetCode完全刷不到这种题。题目大致是:给你一组二维点坐标和初始簇中心,要求实现一轮K-Means迭代,输出更新后的簇中心。
思路分两步:
- 对每个点,计算它到所有簇中心的距离,把它归属到最近的簇。
- 对每个簇,计算簇内所有点的均值,作为新的簇中心。
我用Python写了个简洁版本:
def kmeans_one_iter(points, centers, k): clusters = [[] for _ in range(k)] for p in points: dists = [sum((p[i] - c[i]) ** 2 for i in range(len(p))) for c in centers] idx = min(range(k), key=lambda i: dists[i]) clusters[idx].append(p) new_centers = [] for cluster in clusters: if cluster: m = len(cluster) new_centers.append([sum(p[i] for p in cluster) / m for i in range(len(cluster[0]))]) else: new_centers.append([0.0] * len(centers[0])) return new_centers注意事项:
- 距离用欧氏距离的平方就行,不用开根号,省去浮点误差还省计算。
- 空簇的处理很关键。严格来说要重新初始化或保留原中心,我这里是先置零,如果平台有时间限制,后续可以把空簇重新赋值为一个随机点。
- 输入的数据可能是float,最小化距离的索引在Python里用
min(range(k), key=...)比较高效。
这种考察方式其实是提醒你:AI工程师的笔试不只是LeetCode,机器学习经典算法也得能手写。你天天调sklearn,但让你脱离库实现一轮迭代就卡壳,说明基本功还是不牢。
4.3 编程题通用答题策略
在线笔试平台的判题机制不一样,有的只看输出结果,有的还有部分用例分。我的经验是:
- 先写暴力解拿部分分,保证不是0分。
- 再逐步优化,而不是一开始就冲最优解。
- 代码里多写注释,告诉判卷人(如果人工看的话)你的思路。
- 一定要自测示例输入,确认输出格式和题目要求完全一致,包括换行、空格、小数位数。
2018年那会儿,有的平台输入输出格式比较死板,多余打印一行调试信息都会判错。别问我是怎么知道的。
5. 在线笔试的答题策略与踩坑实录
5.1 时间分配的“二八法则”
90分钟做20道选择题加2-3道简答加2道编程题,时间其实偏紧。我的策略是:
- 选择题控制在30分钟内,遇到不会的先用排除法选一个,标记下来,别恋战。
- 简答题每道控制在10分钟内,答要点、画框架、列公式,别写小作文。
- 编程题给每道留20分钟以上。
很多同学死磕一道简答题,结果编程题只剩10分钟,能写好才怪。笔试的目标是总分最大化,不是每道题都完美。
5.2 环境与IDE适应
2018年在线笔试平台一般提供网页版IDE,那个自动补全和代码高亮都还行,但运行速度一般。你平时在本地用PyCharm/Jupyter写习惯了,突然换到网页编辑器,手指都像是别人的。
我的建议是考前一定用牛客网或赛码网刷几道题,熟悉网页编辑器的操作节奏,特别是缩进和括号匹配。另外,Python版本可能有差异,优先写兼容Python 2和Python 3的代码,像print加不加括号这种问题,在平台上直接可以决定你第一题能不能过。
5.3 输入输出格式的坑
在线笔试最常见的翻车点不是算法不会,而是输入输出格式不对。题目给的是“第一行一个整数n,第二行n个整数”,你必须一行一行读,不能用input().split()一把梭,如果某一行有多余空格就会出错。
我写了一个通用模板:
import sys def main(): data = sys.stdin.read().strip().split() if not data: return n = int(data[0]) nums = list(map(int, data[1:1+n])) # 业务逻辑 if __name__ == "__main__": main()这样能用,但要注意:如果业务是先读一行处理一行,比如多组测试用例,一次性读取反而容易搞混顺序。务必看清题目约定。
5.4 在线笔试平台的“隐形规则”
在线笔试平台一般有防作弊机制,比如切屏超过几次会被警告甚至强制交卷。我当时接到过“检测到切屏,请保持专注”的提醒,吓得赶紧关掉所有其他窗口。
另外,千万不要在代码里写明文输出特殊字符来标记做题痕迹,比如打印“this is candidate answer”,这种操作一旦被人工复核看到,轻则扣分重则取消成绩。写代码就老老实实提交。
5.5 简答题的答题模板
简答题最怕的是“会但说不清楚”。我总结了一个万能框架:
- 先一句话给结论。
- 再用公式或图示解释核心原理。
- 最后结合业务场景说“所以在这个场景下应该怎么选”。
比如问“为什么用AUC而不是准确率进行评估”,回答顺序是:
- 准确率在样本不平衡时会被多数类主导。
- AUC衡量的是模型对所有样本排序能力,不依赖具体阈值。
- 在点击率预估这类正样本稀少的业务场景,AUC能更稳定地反映模型效果。
这种回答问题的方式,能让面试官觉得你不是背答案,而是真的理解这个指标背后反映的业务含义。
5.6 心态层面:别让一两道题毁掉整场考试
在线笔试和面试不一样,你看不到面试官的表情,遇到一道完全没思路的题很容易陷入焦虑。我的体会是:遇到不会的题,先跳过,把所有能拿的分拿到手,再回头啃硬骨头。有些题看着很难,但写几行暴力解可能就过了部分用例,分数照样能拿。
那年我做一道关于LSTM的反向传播推导题时完全卡壳,当时选择先去做后面的编程题,最后剩10分钟回来硬写了一个框架,虽然不完整,但至少写了前向和损失部分的公式,没让这道题彻底空掉。后来复盘想想,这种“先保底再攻坚”的节奏,大概是那次笔试我能顺利进入面试环节的重要原因。
说到底,这类校招笔试不是要你拿满分,而是要让面试官看到你的思维方式和工作潜力。把基础数学原理吃透,把经典代码实现烂熟于心,把业务场景和技术选型结合起来讲清楚,你的通过概率自然就上去了。