8月底收到OPPO秋招算法岗笔试邮件的时候,我正在牛客上刷模拟题。切到赛码平台一看,90分钟,题型有单选、多选、简答和两道编程题,题目质量不低,而且和我之前刷的互联网大厂题库很不一样——里面居然出现了图像锐化算子、PID控制原理、音频重采样这些“很手机厂商”的题。当时我就意识到,这场算法岗笔试不能只按LeetCode的套路准备,得先把手机厂商的业务场景吃透。
这篇文章把这套笔试题涉及的核心考点完整复盘一遍:KMP的next数组怎么算、KL散度和ELBO会怎么考、Sobel和拉普拉斯算子背后的影像逻辑、PID和FOC为什么出现在手机厂商笔试里,以及手撕代码题的常见套路和踩坑教训。准备投OPPO或者其他手机厂商算法岗的同学,建议先读完再上考场。
1. 这场笔试的底色:手机厂商算法岗到底在考什么
1.1 算法岗不是只有一个名字
OPPO秋招的算法岗在官网申请时能看得很清楚,不是笼统的“算法工程师”一个岗位,而是分成影像算法、音频算法、机器学习/数据挖掘、控制算法、NLP算法、搜索推荐等多个子方向。虽然都叫算法岗,笔试的考察重心差异很大。
影像算法岗侧重点在图像处理、ISP流程、深度学习模型压缩与部署;音频算法岗会考信号处理、滤波器设计、重采样和回声消除;小布助手相关岗位偏向NLP和语音;互联网服务方向则大量考察传统机器学习和深度学习基础;硬件周边还会考控制算法,比如充电电压电流控制、马达驱动。
这也是为什么很多人在网上搜“OPPO算法岗笔试”的时候,会觉得考点很杂。不是题目出得乱,而是手机厂商的算法岗本身就是多线并行的。投递之前先看清楚自己报的是哪个子方向,复习才有针对性。
1.2 从业务线反推考点
手机厂商的核心卖点是什么?影像、快充、外观、系统流畅度。这些卖点背后对应的算法岗需求就非常清晰了:
- 影像:超分、降噪、HDR、美颜、夜景增强,涉及Sobel、拉普拉斯这些经典算子,也涉及深度学习图像分类和生成模型。
- 快充:VOOC闪充的电压电流控制,涉及PID这类的控制算法。
- 马达:线性马达的驱动与振动波形调校,涉及FOC控制。
- 音频:通话降噪、扬声器音效、多设备协同,涉及重采样和滤波器设计。
- 小布助手:语音唤醒、NLP对话、搜索推荐,涉及Transformer、检索模型、用户画像聚类。
- 智能制造:手机产线的缺陷检测,涉及工业异常检测算法。
所以热词里出现“pid算法在crps psu power的作用”“音频重采样算法”“sobel算法”,并不是网络搜索的偶然联想,而是真实业务刚需。我印象最深的是一道简答题:给了一小段快充电源控制场景,问PID三个环节各自起什么作用。如果只看算法导论,肯定懵;但如果你知道OPPO做闪充,就知道这道题考的就是控制环路的比例、积分、微分各自解决什么问题。
1.3 线上笔试的真实体验
2023年的线上笔试用的是赛码平台,整体体验还算稳定。题型分布大致是:20道选择填空,覆盖数据结构、算法复杂度、机器学习基础;5道多选题,集中在深度学习和数学基础;2道简答题,通常是场景题;最后2道编程题,难度中等偏上。
需要注意两点:一是多选题的计分规则,有些平台漏选给一半分,有些选错直接0分,开始前一定看清说明;二是编程题不要求全部测试用例通过,但能过部分就有分,所以哪怕没完全做对,也要把暴力解的代码交上去,别留空。
时间安排上,选择题平均每题不到两分钟,不会的果断跳过,千万别卡在某一题上。我当时在KMP那道概念题上多花了几分钟,后面编程题时间就有点紧,这是个教训。
2. 基础算法轮:从KMP的next数组到贪心与模拟退火
2.1 KMP与next数组:一道题暴露定义不统一
热词里专门有“在kmp算法中,对于模式串p=‘abacaba’,其next数组(next[i]定义为...)”,这道题在2023年秋招笔试中确实出现了,而且很多人在网上讨论答案不统一。
不统一的根源是next数组有两种常见定义。一种定义是:next[i]表示模式串前i个字符(或者说以i结尾的子串)的最长公共前后缀长度;另一种定义是:next[i]表示第i位失配时应该跳转到的位置。两种定义算出来的结果不同,笔试时必须先看清题目给的是哪一种。
我按下标从0开始、next[i]表示p[0..i]这个子串的最长公共前后缀长度来计算:
| i | 子串 | 最长公共前后缀 | next[i] |
|---|---|---|---|
| 0 | a | 无 | 0 |
| 1 | ab | 无 | 0 |
| 2 | aba | a | 1 |
| 3 | abac | 无 | 0 |
| 4 | abaca | a | 1 |
| 5 | abacab | ab | 2 |
| 6 | abacaba | aba | 3 |
所以按这个定义,next数组是 {0, 0, 1, 0, 1, 2, 3}。
计算方法是:对每个位置i,找出子串p[0..i]的所有前缀和后缀,取相同的最大长度。比如i=6时,子串是“abacaba”,前缀有a、ab、aba、abac、abaca、abacab,后缀有a、ba、aba、caba、acaba、bacaba,共同的是a和aba,最大就是3。
需要特别提醒:如果把next定义为失配跳转位置,通常从-1或0开始,结果就完全不一样了。平时刷算法题的时候,建议固定使用一种定义并理解清楚,但真正笔试时一定要按题目给出的定义来,不要下意识套模板。
2.2 排序与快速幂:看似送分实则埋坑
笔试选择题里排序算法是高频考点。冒泡排序、堆排序、快速排序、归并排序的时间复杂度、空间复杂度、稳定性几乎每年都考。其中冒泡排序还会让写C++实现,这道题看着简单,但边界条件容易出错。
void bubbleSort(vector<int>& arr) { int n = arr.size(); for (int i = 0; i < n - 1; ++i) { bool swapped = false; for (int j = 0; j < n - 1 - i; ++j) { if (arr[j] > arr[j + 1]) { swap(arr[j], arr[j + 1]); swapped = true; } } if (!swapped) break; // 优化:本轮无交换则已有序 } }冒泡排序最好情况O(n),最坏和平均O(n²),稳定。堆排序是O(n log n),不稳定,笔试里通常考建堆过程和topK思路,而不太会要求完整手写堆排序。
快速幂是另一个高频考点。它本身不难,但很多人不熟悉取模场景下的写法:
long long fastPow(long long a, long long b, long long mod) { long long res = 1; while (b > 0) { if (b & 1) res = res * a % mod; a = a * a % mod; b >>= 1; } return res; }笔试里快速幂通常会结合组合数取模、模逆元一起考。复习的时候别只背模板,要理解它为什么能把指数降到O(log b)次运算:二进制拆分,把b表示成若干个2的幂的和,a的对应幂次提前算好。
2.3 图论题:贪心、Dijkstra、二分图与Kahn排序
基础算法轮里,图论和贪心是编程题的主要来源。
贪心算法最常见的是区间调度类问题。比如给定若干区间,选择最多的互不重叠区间。解法是先按结束时间排序,然后依次选择结束时间最早的区间。这种题的证明思路是交换论证,笔试简答或面试里会问“为什么贪心是对的”。
Dijkstra算法是图论里的必考内容。笔试编程题如果出“给定加权无向图,求起点到终点的最短路”,标答基本就是堆优化Dijkstra:
vector<int> dijkstra(int n, vector<vector<pair<int,int>>>& graph, int s) { vector<int> dist(n, INT_MAX); dist[s] = 0; priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq; pq.push({0, s}); while (!pq.empty()) { auto [d, u] = pq.top(); pq.pop(); if (d > dist[u]) continue; for (auto [v, w] : graph[u]) { if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push({dist[v], v}); } } } return dist; }注意点:Dijkstra不能处理负权边;稠密图用朴素O(n²),稀疏图用堆优化O((n+m)log n)。笔试里数据范围通常会暗示该用哪种。
二分图HK算法和Kahn排序更多是概念题。Kahn算法是拓扑排序的经典实现,维护入度为0的节点队列,依次取出并减少邻居入度。DC3算法是后缀数组的O(n)构造算法,笔试基本不会让你手写,但选择题里可能会问“以下哪个算法能在O(n)时间内构造后缀数组”。
2.4 粒子群和模拟退火:考的是“知道不知道”
粒子群算法和模拟退火算法这两类元启发式优化算法,在互联网大厂笔试里出现频率不高,但手机厂商和智能制造相关岗位会考,因为产线调度、参数调优、路径规划这些场景真的会用到。
粒子群算法的核心是:每个粒子有位置和速度,记录个体最优pbest和全局最优gbest,迭代更新:
v = w·v + c1·r1·(pbest - x) + c2·r2·(gbest - x) x = x + v
其中w是惯性权重,c1是自我认知因子,c2是社会认知因子。笔试题可能直接问w增大对搜索的影响:w越大,全局探索能力越强;w越小,局部开发能力越强。
模拟退火算法的关键是Metropolis准则:新解比当前解好,一定接受;新解更差,以概率exp(-ΔE/T)接受。温度T越高,接受差解的概率越大,早期不容易陷入局部最优;随着温度下降,接受劣解的概率减小,最终收敛。
这类题答题时做到三步:讲清灵感来源(粒子群来自鸟群觅食,模拟退火来自金属退火),讲清核心步骤,讲清适用场景。不需要写完整代码,但要把关键公式和参数含义写出来。
3. 机器学习与深度学习轮:KL散度、ELBO与模型细节
3.1 KL散度为什么永远是考点
KL散度,也叫相对熵,是机器学习笔试里绕不开的基础概念。公式是:
KL(P||Q) = Σ P(x) log(P(x)/Q(x))
它是用来衡量两个概率分布之间差异的度量,但它不是一个真正的距离。这个“不是距离”的点,经常在选择题里出现。KL散度不满足对称性,KL(P||Q)不等于KL(Q||P),也不满足三角不等式。
为什么KL散度这么重要?因为深度学习的很多目标函数都能跟它扯上关系:交叉熵损失本质上就是KL散度加上一个常数项;VAE的损失函数里有KL散度;强化学习PPO算法也用KL散度来限制新旧策略的差异。
笔试题可能会给两个简单的离散分布,让手算KL散度。比如P = {0.5, 0.5},Q = {0.9, 0.1},算KL(P||Q)。这种题要会算,而且要会判断结果是否大于等于0。KL散度的非负性一般用Jensen不等式证明,笔试简答里如果问“为什么KL散度非负”,可以答:因为log是凹函数,所以E[log(X)] ≤ log(E[X])。
另外注意:有的题目用log2底,有的用自然对数ln,算出来的数值不同,但判断大小关系不受影响。
3.2 ELBO与变分推断:VAE推导的得分点
热词里“kl elbo算法原理详解”基本就是冲着VAE和变分推断去的。ELBO(Evidence Lower Bound,证据下界)是变分推断的核心概念。
推导逻辑大致是:我们要算后验分布p(z|x),但很难直接求。于是找一个容易计算的分布q(z|x)去近似它。对log p(x)做变形:
log p(x) = ELBO(q) + KL(q(z|x) || p(z|x))
其中:
ELBO(q) = E_q[log p(x|z)] - KL(q(z|x) || p(z))
因为KL(q(z|x) || p(z|x)) ≥ 0,所以log p(x) ≥ ELBO(q)。这意味着最大化ELBO等价于最小化KL(q(z|x) || p(z|x)),也就是说,让近似的后验分布尽量接近真实后验分布。
在VAE里,ELBO的两项通常被解释为重构损失和正则项。E_q[log p(x|z)]鼓励模型从隐变量z重构出真实数据x,KL(q(z|x) || p(z))约束隐变量分布接近先验分布(通常是标准正态分布)。
笔试考法常见是:给一个VAE的损失函数表达式,问前一项和后一项分别是什么含义。或者是问“变分推断中为什么用ELBO而不是直接优化KL散度”,答案是ELBO是log p(x)的下界,可以直接计算梯度优化,而KL(q||p)里的p(z|x)是未知的。
3.3 KNN的三个应用能力与聚类算法
热词里有一条“knn算法的应用能力包括哪三个方面”,这个题很典型。KNN就是K近邻算法,它的三个核心应用能力是:
- 分类:找到样本的K个最近邻,用多数投票决定类别。
- 回归:找到样本的K个最近邻,对标签取平均或加权平均。
- 缺失值填充/异常检测:用最近邻的取值估计缺失值,或者根据到邻居的距离判断是否异常。
这是标准答案,简答题可以直接这样写,再每个方向补一句解释。KNN的思路本身简单,但笔试常考它的特点:非参数方法、训练时没有显式学习过程、预测时计算量大、对特征尺度敏感所以通常需要标准化。
聚类算法也是常客。K-Means是最基础的,笔试题可能给出几个二维点,要求手动迭代一次K-Means,算聚类中心变化。步骤是:随机初始化K个中心,把每个点分给最近的中心,然后重新计算每个簇的中心,直到收敛。K-Means对初始值敏感、对离群点敏感,这也是考点。
在手机厂商场景里,聚类更多用于用户分群、语音/图像特征聚类、异常检测中的先验建模。工业异常检测算法在产线质检场景里很重要,常见思路包括重构误差法(用自编码器重构正常样本,重构误差大的判为异常)、特征距离法(提取正常样本特征,看目标样本特征与正常特征集合的距离)、以及PatchCore这类基于记忆库的方法。
3.4 从图像分类到强化学习:新模型与新范式的选择题
深度学习部分的选择题经常涉及基础模型和最新进展。2023年前后,EVA-02这类视觉Transformer增强模型出现在热词里,笔试题不会深挖模型细节,但会考“视觉Transformer和CNN的核心区别是注意力机制”“图像分类模型的发展脉络”这类概念。
强化学习今年在热词里也频出。强化学习的基本要素是状态、动作、奖励、策略、价值函数。策略梯度方法的核心思想是直接对策略进行梯度上升,提高高回报动作的概率。PPO算法里用KL散度限制新旧策略的差异,防止更新步子太大导致性能崩坏。
笔试里强化学习不会考得太深,一般就是选择题:给出几个算法问哪个不是强化学习算法,或者问reward shaping的作用。掌握基本概念就够了,不用去细推PPO的裁剪目标函数,除非你投的岗位明确做RL方向。
4. 业务场景题:影像、音频、快充与搜索里的算法考法
4.1 图像算法:Sobel、拉普拉斯与锐化背后的ISP逻辑
在手机影像算法岗的笔试里,Sobel算子和拉普拉斯算子属于基础中的基础。它们都不是深度学习内容,而是传统图像处理里的边缘检测和锐化工具,但在ISP流程里依然有位置,比如用于边缘增强、夜景细节提取。
Sobel算子有两个卷积核,分别检测水平边缘和垂直边缘:
Gx = [[-1,0,1],[-2,0,2],[-1,0,1]] Gy = [[-1,-2,-1],[0,0,0],[1,2,1]]
计算每个像素的梯度幅值,通常近似为|Gx| + |Gy|,也可以取平方和的平方根。Sobel相比单纯的梯度算子加了加权平均,抗噪能力更好一些。
拉普拉斯算子是一个二阶微分算子,常用卷积核是:
[[0,1,0],[1,-4,1],[0,1,0]]
或者带对角线的版本:
[[1,1,1],[1,-8,1],[1,1,1]]
图像锐化的基本公式是:锐化结果 = 原图 + 系数 × 拉普拉斯结果。这样做能让边缘对比度增强,画面显得更清晰。手机拍照里的“清晰度”调优,很多就是从这类算子起步的。
笔试如果出简答题,可以这样答:Sobel算子计算一阶梯度,突出边缘位置;拉普拉斯算子计算二阶梯度,对边缘敏感且对噪声更敏感,所以实际应用中通常先做平滑再去锐化。如果允许写伪代码,可以用Python思路:
# 拉普拉斯锐化(伪代码示例) import numpy as np from scipy import signal laplacian_kernel = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]]) lap = signal.convolve2d(gray_img, laplacian_kernel, mode='same') sharpened = gray_img - lap # 中心为负时用减想拿高分,要能说清楚“为什么锐化要加边缘信息”,以及“在手机夜景模式下直接拉高锐化会放大噪声”——这是实际调优中常见的坑。
4.2 音频重采样:从信号处理角度拆解
音频重采样算法会出现在音频算法岗的试卷里,但在通用算法岗的简答题里也出现过,毕竟做手机不可能不处理音频。重采样就是从44.1kHz转到48kHz这类采样率转换。原理上可以分两步理解:
- 上采样:先插值,增加采样点数。比如44.1kHz转48kHz,需要先插值到一个公倍数,再抽取。
- 抽取:按新采样率取点。直接抽取会导致频谱混叠,所以抽取之前必须经过低通滤波器,去掉采样率变化后超出奈奎斯特频率的部分。
最简单可演示的方法是线性插值:目标采样点落在两个源采样点之间,用线性插值计算。但实际工程里会用多相滤波器(polyphase filter),效率更高。笔试题如果问“为什么重采样后会有质量损失”,可以从滤波器的非理想特性、量化误差、以及重采样链路上多次插值抽取带来的累积误差这几个角度回答。
这里多说一句:OPPO做音频算法的同学,日常会涉及通话降噪、立体声录制、扬声器保护等等,重采样只是音频链路里很小的一个环节,但笔试拿它当“场景题”很合适,因为它能区分懂信号处理和不懂信号处理的人。
4.3 PID和FOC:手机厂商才爱考的控制算法
热词里“pid算法在crps psu power的作用”看上去很拗口,其实就是问PID在电源(CRPS电源、PSU供电单元)功率控制里的角色。OPPO做闪充,快充充电头里的电压电流控制环路离不开PID。
位置式PID控制公式:
u(k) = Kp·e(k) + Ki·Σe(i) + Kd·(e(k) - e(k-1))
其中e(k)是当前误差。比例项P按当前误差大小输出控制量,让系统快速靠近目标;积分项I消除稳态误差,因为只要误差一直存在,积分项就会不断累积;微分项D抑制误差变化速度,防止超调和震荡。
笔试简答可能会问:如果充电电流出现稳态偏差,应该调大哪个参数?答案是增加Ki;如果系统响应太慢,优先调大Kp;如果存在震荡,考虑增加Kd。FOC(磁场定向控制)在OPPO的线性马达驱动场景中会出现。FOC的核心是对三相电流做坐标变换:先是Clarke变换,把abc三相坐标系变到αβ静止坐标系;再做Park变换,变到dq旋转坐标系。在dq坐标系下,d轴和q轴的电流可以解耦控制,常用策略是id=0控制,让q轴电流完全对应电磁转矩。笔试考FOC通常是概念题,问Clark变换和Park变换的作用分别是什么。
需要注意的是,控制算法一般不会出现在通用算法工程师的试卷里,更多是出现在硬件相关的算法岗位上。如果简历里写了马达驱动、充电控制相关的项目,考前一定要把这些概念准备好。
4.4 搜索、推荐与规则引擎:BM25、聚类与Rete
这几项更多对应OPPO的互联网服务方向,比如ColorOS里的应用商店搜索、主题商店推荐、浏览器信息流。
BM25是文本检索里的经典打分函数。公式可以记成:每个查询词的权重乘以词频饱和度因子,再叠加文档长度归一化。笔试可能只要求解释它在做什么:对每个出现在文档中的查询词,计算它对文档相关性的贡献,词频越高贡献越大,但不会线性增长;文档越长,词频的贡献会被惩罚,防止长文档天然占便宜。
聚类的主要作用是做用户分群、物品召回。比如把用户按行为特征聚类成几个群体,然后在每个群体里做协同过滤,比全局计算相似度效率更高。K-Means在推荐系统里的应用很广。
Rete算法是规则引擎Drools的核心。笔试题可能会问“Rete算法的原理和事实匹配过程”,回答框架是:Rete把规则条件拆成原子条件,构建Alpha网络和Beta网络。事实进来后,先经过Alpha网络做单条件匹配,结果保存到alpha memory;然后在Beta节点完成多条件连接匹配,中间结果缓存下来。这样新增事实时,不需要对全部规则重新匹配,只用处理受影响的部分,因此适合大量规则和频繁更新的场景。
这几块内容不需要深入源码,但概念层面要能讲清楚“输入是什么、过程分几步、优点是什么”。
4.5 井字棋与Minimax:博弈题的入门姿势
热词里“井字棋minimax算法实现详解”非常形象——笔试不会真让我写一个完整游戏,但Minimax作为博弈树搜索算法,在简答题和编程题里都有可能出现。
Minimax假设对手永远选最不利于己方的走法。当前玩家最大化自己的收益,对手最小化你的收益。树的叶子节点是胜负或平局得分,回传时交替取max和min。如果加上Alpha-Beta剪枝,可以大幅减少搜索节点数量。
笔试考Minimax通常是给一个简单的博弈树,让你标注每个节点的值。做题要点:从叶子向上,奇数层取最大,偶数层取最小,取决于谁走棋。如果写出伪代码,核心是递归:
def minimax(node, depth, maximizing): if depth == 0 or node.is_terminal(): return node.evaluate() if maximizing: return max(minimax(child, depth-1, False) for child in node.children) else: return min(minimax(child, depth-1, True) for child in node.children)井字棋本身搜索空间小,适合当例题,笔试看到这类题不要慌,本质就是深搜+回溯。
5. 手撕代码题:几个高频题型的实打实套路
5.1 在线笔试平台与输入输出处理
在线笔试的编程题难点往往不是算法本身,而是输入输出。赛码、牛客这些平台的题目,输入是标准输入,输出是标准输出,和本地IDE里写main函数读文件不一样。
第一件事是掌握多组输入的处理。很多题会写“给定多组测试数据,每组第一行是n”,需要用while(cin>>n)或者while(scanner.hasNext())循环读取。第二件事是确认数据范围:如果n达到10^9,就要警惕int溢出,除法、模运算、中间结果全部用long long。
还有一个小建议:正式笔试开始前,先花2分钟建好本地的代码模板,包括常用的头文件、快读函数、一堆STL的引入。赛码是支持本地编译的,把模板提前写好,能省不少时间。
5.2 TopK与堆排序:一个模板打天下
TopK问题是笔试里出现频率极高的编程题。给一个无序数组,找出第K大的数,或者最大的K个数。解法可以是排序(O(n log n))、快排partition(O(n)平均)、小顶堆(O(n log K))。
笔试环境里最稳妥的是小顶堆,因为不容易写错,而且代码量小:
vector<int> topK(vector<int>& nums, int k) { priority_queue<int, vector<int>, greater<int>> minHeap; for (int x : nums) { minHeap.push(x); if ((int)minHeap.size() > k) minHeap.pop(); } vector<int> res; while (!minHeap.empty()) { res.push_back(minHeap.top()); minHeap.pop(); } return res; }原理是维护一个大小为K的小顶堆,堆顶永远是堆里最小的元素。每来一个新元素,放进堆里,如果堆大小超过K,就弹出堆顶,这样堆里始终保留当前遇到的最大K个数。这个套路背后其实就是堆排序的核心操作——堆化。
如果题目进一步问“这个堆的建堆复杂度是多少”,要能答出来:只对前K个元素建堆是O(K),后续每个元素push+pop的操作是O(log K),整体O(n log K),比排序更快。
5.3 Dijkstra堆优化:图论题的标答模板
图论题里最常出现的就是最短路。题目描述通常很直白:有N个城市、M条路、求从城市1到N的最短时间。直接用堆优化Dijkstra就行。
我在前面给过代码,这里补充几个容易踩的坑:
- 图是稀疏还是稠密?N很大、M不大的时候,必须用邻接表存图,不要用邻接矩阵。
- 堆里存的是pair<距离, 节点>,距离放第一个,因为pair默认按第一个元素排序。
- 用
if (d > dist[u]) continue;跳过过期的堆节点,这个剪枝不能省。
如果题目带负权边,Dijkstra就不能用了,要考虑Bellman-Ford或SPFA,但秋招笔试里出现负权边的概率不高,优先保证Dijkstra熟练。
5.4 快速幂与组合数取模:数论题的基本功
赛码和牛客的编程题里数论题不算多,但组合数取模是常客,特别是结合动态规划的时候。组合数C(n, k)的值可能非常大,题目会要求对1e9+7取模输出。
关键点有两个:一是用逆元计算组合数时,n的范围如果小于1e6而且要求大量组合数,可以提前预处理阶乘和逆元;二是如果n很大(1e9级别),就要用Lucas定理。笔试一般考不到Lucas那么深,但快速幂是必备的,因为逆元计算要用它。
long long mod = 1e9 + 7; long long modInv(long long a) { return fastPow(a, mod - 2, mod); }费马小定理,模数是质数时a的逆元是a^(mod-2),这个知识点在组合数取模题里高频出现。
5.5 时间复杂度的估算与面试官视角
做编程题之前先看数据范围:n ≤ 10^5,O(n²)大概率超时,得想O(n log n);n ≤ 10^3,O(n²)可以接受;n ≤ 10^7,基本只能O(n)线性扫一遍。这能帮你快速判断该用什么算法。
面试官或出题人看的是“你能否独立分析问题、选择合适算法、写干净代码”。所以编程题别一上来就写,先想:暴力怎么做?暴力复杂度是多少?能不能用哈希表、堆、二分、并查集优化?把思考过程写进注释里,即使最后没完全过,阅卷时也能看到思路。
6. 备考优先级与我的踩坑复盘
6.1 按目标岗位分配复习精力
| 目标岗位 | 复习优先级 |
|---|---|
| 通用算法岗(搜索/推荐/机器学习) | 数据结构与算法、机器学习基础、深度学习基础、推荐系统/搜索 |
| 影像算法岗 | 图像处理算子、卷积神经网络、ISP基础、图像分类、模型部署 |
| 音频算法岗 | 信号处理基础、滤波器设计、重采样、麦克风阵列、音频编解码 |
| 控制算法岗 | PID、FOC、卡尔曼滤波、仿真建模、C语言编程 |
| NLP/语音算法岗 | Transformer、BERT、文本分类、语音唤醒、ASR基础 |
我的建议是:别把大量时间花在冷门算法上。DC3、HK算法这类掌握概念就行,真正能拉开差距的是基础算法的熟练度和对业务场景的理解。选择题里大量考察的是“最基础的结论”,比如快排不稳定、K-Means对初始值敏感、KL散度不对称,这些只要复习到位就能拿分。
6.2 我在这次笔试中的三个教训
第一个教训是KMP的next数组浪费了太多时间。题目明明给了定义,我还是下意识用了刷题时习惯的定义,算出来跟选项对不上,又回头重算,白白损失了5分钟。笔试里定义不统一的算法题很常见,务必先读题给的公式。
第二个教训是编程题的数据类型。手撕Dijkstra时,我以为N不超过10^5,距离用int就够了,结果测试用例里路径总长超过int边界,溢出导致部分用例WA。后来凡涉及距离、乘积、累加的题,一律用long long,这个习惯也影响了我后续所有笔试和面试。
第三个教训是多选题的“宁缺毋滥”策略。有些平台多选题多选了会倒扣分,只看正确选项个数给分,这时候不确定的选项就不要选。我一开始按“全部选对才得分”的习惯,漏选了几个,导致2道多选0分。开始前一定看清计分规则。
6.3 给下一届的建议
如果你现在正准备投手机厂商的算法岗,我给几条实操建议:
第一,把手机厂商的业务拆开看。影像、音频、充电、马达、小布助手,每个方向对应的算法栈都不一样,挑自己熟悉的1-2个方向重点准备。投递时不要海投所有算法