又到了一年一度的校招季,后台不少学弟学妹在问“技术类在线笔试到底考什么”“怎么准备才能不挂”。这让我想起自己当年参加小红书2019年校园招聘技术类在线笔试第一批的经历,那次笔试给我留下的印象挺深——题目难度分布合理、节奏紧凑、平台操作也比较规范,而且有几道题非常典型,值得拿出来复盘。
我尽量把当时现场的情况还原出来,包含完整的题目拆解、思路推导、代码实现和踩坑记录。如果你今年也要投技术岗校招,这份内容应该能帮你少走不少弯路。
1. 笔试基本信息与题型分布
1.1 笔试平台、时间与整体安排
在线笔试用的是牛客网的平台,这个在当年已经算是校招标配了。一共4道编程题,限时120分钟,全程监控开启,需要共享屏幕。我当时用的是C++,也有人用Java、Python,平台都支持。
说一下我当时对这场笔试的整体感受:题目不算特别偏,但有一个很明显的特征——前三题是“保底题”,最后一题是“区分题”。前三题只要能扎实刷过《剑指Offer》或者LeetCode热门题,基本都能AC;最后一题如果不提前接触过类似模型,很容易卡在超时上。
时间分配上,我个人建议是这样:
| 题型 | 建议用时 | 目标 |
|---|---|---|
| 第1题(简单题) | 15-20分钟 | 必须AC |
| 第2题(中等题) | 20-30分钟 | 尽量AC |
| 第3题(中等偏难题) | 25-35分钟 | 尽量AC |
| 第4题(压轴题) | 30-40分钟 | 尽力拿部分分 |
1.2 题目难度梯度分析
4道题,难度是明显递进的。第1题是典型的字符串模拟题,考察基本编码能力;第2题是数组处理加双指针思维;第3题是动态规划,模型不复杂但边界条件多;第4题是数据结构优化题,暴力解法好想但过不了全部数据。
这里我要多说一句:很多人看见“难度递进”就理解为“从第1题做到第4题”,这是最大的误区。在线笔试的判分规则通常是按通过用例数给分,而不是按题号加分。所以一个很现实的策略是:先把4道题都看一遍,每道题先用暴力解法拿部分分,最后再回来优化。这样哪怕最后一题没AC,至少能拿到60%-70%的用例分。
后面我会结合具体题目,详细讲解这个策略怎么落地。
2. 四道真题完整复盘与解题思路
这一部分我尽量把题目、思路、代码、踩坑点全部展开,不省略关键推导过程。题目是我凭记忆还原的,核心数据范围和考察点是可以确定的。
2.1 第1题:字符串连续字符压缩
题目描述:给定一个字符串,将其中连续重复的字符压缩为“字符+出现次数”的形式。例如aaabbbcddd压缩后为a3b3c1d3。要求不改变字符原有顺序,且压缩后的字符串长度必须小于原串长度,否则返回原串。字符串长度不超过1000。
这道题属于“签到题”的级别。我当时是先写一个循环遍历,记录当前字符cur和计数cnt,遇到不同字符时把cur和cnt拼到结果里,最后再做一次长度比较。
def compress(s: str) -> str: if not s: return s res = [] cur = s[0] cnt = 1 for i in range(1, len(s)): if s[i] == cur: cnt += 1 else: res.append(cur + str(cnt)) cur = s[i] cnt = 1 res.append(cur + str(cnt)) compressed = ''.join(res) return compressed if len(compressed) < len(s) else s这里有一个很容易忽略的点:题目要求“压缩后长度必须小于原串长度,否则返回原串”。如果原串是abc,压缩后是a1b1c1,长度反而更长,必须返回原串。我见过有人栽在这个“小于”还是“不大于”的判断上,如果是等于的情况返回原串,照样能过,但其实题目明确写的是“小于”。
复杂度是 O(n),没啥悬念。这道题就是考察你能不能把逻辑写干净,不要出边界 bug。
2.2 第2题:数组按频率从高到低排序
题目描述:给定一个整数数组,请按照元素出现频率从高到低排序。如果两个元素的频率相同,则按元素值从大到小排列。数组长度不超过10万,元素值范围在 -10^9 到 10^9 之间。
这道题的关键在于排序规则的组合。先统计频率,然后对元素去重后排序,排序的比较函数需要同时考虑两个维度:频率降序 + 元素值降序。
我当时的做法是先用哈希表统计频率,然后把键值对转成列表,用自定义比较函数排序,最后按顺序输出。
from collections import Counter def freq_sort(arr): cnt = Counter(arr) # 按频率降序,频率相同按元素值降序 items = sorted(cnt.items(), key=lambda x: (-x[1], -x[0])) res = [] for val, freq in items: res.extend([val] * freq) return res这个做法本身没什么问题,时间复杂度是 O(n log n),注意看数据范围:长度10万,O(n log n) 是稳的。但有几个细节值得提:
第一,比较函数怎么写。Python 里用key参数时,可以取负号来模拟降序,因为元组是字典序比较。(-x[1], -x[0])表示先按频率降序,再按值降序。C++ 的话用sort传入cmp函数,注意cmp要写成严格弱序。
第二,元素值范围很大,有负数,所以你不能用“数组下标计数”的方式,必须用哈希表。
这道题其实想考察的是:你会不会用哈希表做统计,以及你会不会写自定义排序规则。属于面试笔试中的“万金油”题。
2.3 第3题:最小编辑距离变种
题目描述:给你两个字符串 A 和 B,你可以对 A 进行三种操作:插入一个字符、删除一个字符、替换一个字符。求将 A 变成 B 所需的最少操作次数。两个字符串长度均不超过1000。
看到“最小编辑距离”,不用犹豫,直接往动态规划上想。这是 LeetCode 72 题的原题模型。
定义dp[i][j]表示将 A 的前 i 个字符变成 B 的前 j 个字符所需的最小操作次数。初始化时dp[i][0] = i(删除 i 次),dp[0][j] = j(插入 j 次)。状态转移分两种情况:
- 如果
A[i-1] == B[j-1],说明当前字符不用额外操作,dp[i][j] = dp[i-1][j-1] - 如果不相等,考虑三种操作的最小值:
- 删除
A[i-1]:dp[i-1][j] + 1 - 插入一个字符匹配
B[j-1]:dp[i][j-1] + 1 - 替换
A[i-1]为B[j-1]:dp[i-1][j-1] + 1
- 删除
def min_distance(a: str, b: str) -> int: 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 for j in range(m + 1): dp[0][j] = j 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, # 删除 dp[i][j - 1] + 1, # 插入 dp[i - 1][j - 1] + 1 # 替换 ) return dp[n][m]这道题最大的坑不在算法本身,而在字符串长度的边界。1000 的长度,二维数组是 1001×1001,大约 100 万个元素,内存完全没问题。但如果你写dp = [[0] * (m + 1)] * (n + 1),那就是把同一行引用了 n+1 次,修改任意一行都会影响其他行,结果全错。这种 Python 初始化写法在笔试里是高频翻车点,建议用列表推导式。
另外,在这个基础上,有些笔试题会加一个“只允许插入和删除,不允许替换”的限制,那状态转移就只剩两个分支。我当时写题的时候先看了题目问的是“三种操作”还是“两种操作”,确认完再动笔,这能避免返工。
2.4 第4题:最大子数组和模 m 问题
题目描述:给定一个长度为 n 的数组(n 不超过 10^5)和一个正整数 m,求数组中某个连续子数组的和模 m 的最大值。
这道题是经典问题的一个变体:最大子数组和的进阶版。普通的最大子数组和可以用 Kadane 算法 O(n) 解决,但加上“模 m”之后,贪心失效了,因为局部最优不能推导出全局最优。
我先说暴力思路:枚举所有子数组,计算区间和再取模,时间复杂度 O(n^2),n 是 10^5 的话完全跑不动。笔试时如果只能写暴力,大概只能过 20%-30% 的用例。
正解需要数学推导。先算前缀和pre[i] = (a[0] + a[1] + ... + a[i-1]) % m,那么区间[l, r]的和模 m 等于(pre[r] - pre[l]) % m。要最大化这个值,可以分两种情况讨论:
- 如果
pre[r] >= pre[l],那么(pre[r] - pre[l]) % m = pre[r] - pre[l],想让值最大,就是让pre[l]尽量小 - 如果
pre[r] < pre[l],那么(pre[r] - pre[l]) % m = pre[r] - pre[l] + m,想让值最大,就是让pre[l]尽量大
遍历前缀和数组时,我们需要在已经遍历过的前缀和中,找到“比当前值大的最小值”和“全局最小值”。这里就需要有序容器。C++ 可以用set或map,Python 可以用bisect来维护有序列表。
from bisect import bisect_right, insort def max_subarray_mod(arr, m): pre = 0 ans = 0 # sorted_list 维护已出现的前缀和 sorted_list = [] insort(sorted_list, 0) # 前缀和初始为0 for x in arr: pre = (pre + x) % m # 找到第一个 > pre 的元素 idx = bisect_right(sorted_list, pre) if idx < len(sorted_list): ans = max(ans, (pre - sorted_list[idx] + m) % m) else: # 没有比 pre 大的,那就用全局最小 ans = max(ans, pre) insort(sorted_list, pre) return ans有人可能不理解pre的更新逻辑:pre其实是“当前前缀和模 m”,每一次更新完,我们就看一下“跟哪个历史前缀和相减能让结果最大”。上面代码里(pre - sorted_list[idx] + m) % m处理的是“历史前缀和比当前大”的情况,因为模运算可能是负数,所以要加 m 再取模。
这道题是典型的数据结构优化题,考察点包括前缀和、模运算、以及有序容器的高效维护。笔试时如果只写暴力,能拿部分分;写出有序集合优化版本,基本就是头部选手了。
3. 时间分配策略与做题顺序
3.1 遇到难题先跳,先拿基础分
我在上面已经提到,在线笔试的判分跟“一锤定音”的考试不一样,它按通过用例数算分。这意味着,你花 40 分钟死磕最后一题,不如用 10 分钟写一个暴力版本拿 30% 的用例分,然后把时间拿回去确保前两题 100% AC。
按照当时的题目难度分布,我的实际时间安排是:
- 先花 5 分钟快速浏览全部 4 道题,确认难度梯度和自己熟悉的题型
- 第 1 题直接 AC,用时约 12 分钟
- 第 2 题看完就知道是哈希表 + 排序,约 20 分钟 AC
- 第 3 题是最小编辑距离,想到了 DP 但仔细想边界条件,约 30 分钟提交
- 第 4 题先写了暴力版,过了一部分用例,再优化成有序集合版,最后大概剩 10 分钟
这里有个细节值得分享:写第 3 题 DP 的时候,我没有一上来就写完整代码,而是先在草稿纸上把 dp 数组的维度、初始化的含义、状态转移的三种情况列清楚,再动键盘。这样看起来浪费了 3-5 分钟,实际上避免了写出“看起来对但边界全错”的代码。
3.2 笔试代码的调试技巧
在线笔试的调试环境和本地 IDE 还是有区别的。牛客网的编辑器没有智能提示,也不能打断点,所以你需要在写代码时格外注意变量名和缩进。
我当时发现自己第 3 题有个边界 bug:当字符串为空时,dp[0][j]和dp[i][0]的初始化会出问题。这个是在本地跑测试用例时发现的,我加了一个if not a or not b的提前处理,才稳下来。建议你们在提交前至少测试这几类边界:
- 输入为空的情况
- 数组/字符串长度为 1 的情况
- 全部元素相同的情况
- 所有元素都不同的情况
- 数据量最大的极端情况,看一眼代码是否超时
3.3 关于“部分分”的执念
很多人有个心理误区:觉得写暴力解法“丢人”,或者觉得“要么 AC,要么不做”。这是校招笔试里最亏的心态。在线笔试是机器评分,暴力解法只要用例跑得过,就有分。哪怕只有 20% 的用例通过,在面试官筛选简历时,这个分数可能比“0 分”强十倍。
我当年参加过不少笔试,见过太多人卡在最后一题“想不出最优解就不提交”,结果是那道题 0 分。反过来,有些人第一眼就知道没有最优解思路,先写个暴力,拿到部分分,再慢慢优化。后者的通过率明显高很多。
4. 笔试题背后的考点与能力模型
4.1 从题目反推面试官想考察什么
校招在线笔试不像竞赛题那样追求“偏、怪、难”,它有很明显的筛选逻辑。从这 4 道题来看,面试官其实在考察几个基础能力:
第一题考“编码基本功”。字符串压缩是一个纯模拟题,没有任何算法含量,就是看你能不能写出清晰、无 bug 的代码。这种题挂人的原因几乎都是:边界没处理好、循环索引写错、忘记看返回条件。
第二题考“对常见数据结构特性的掌握”。哈希表统计频率谁都会,但能不能把“多关键字排序”写对,体现了你对比较函数和排序稳定性的理解。
第三题考“动态规划的基础建模能力”。编辑距离是 DP 入门必学题,但很多人只看过题解,没有自己完整推导过状态转移。笔试一紧张就容易漏掉“替换”操作,或者初始化写错。
第四题考“对经典问题的变形理解”。如果只是背过最大子数组和的 Kadane 算法,遇到模 m 版本会直接懵。这题要的不是“背题”,而是数学推导能力——知道前缀和,知道取模的性质,才知道如何用有序集合去优化搜索。
4.2 笔试与面试之间的衔接关系
小红的面试流程对笔试成绩还挺看重的,笔试分数高,面试时基础知识被问的概率会相对小一些,更多会问项目经历和系统设计;笔试分数低,面试官可能会多问算法题,用来现场验证你的真实水平。
所以我会建议求职者把在线笔试当成“一次免费的全真模拟面试”,不是为了分数而写题,而是为了检验自己到底哪块知识体系还不牢固。笔试暴露出的盲区,如果能在面试前补上,反而比“蒙对一道题”更有价值。
4.3 如何有针对性地准备这类笔试
如果你是今年要参加校招的同学,我建议不要盲目刷题,而是按模块来准备。从这 4 道题可以看出,字符串处理、哈希表与排序、动态规划、前缀和与数据结构,是出现频率极高的大类。你可以建立一个自查表:
| 知识点模块 | 常见题型 | 代表例题 |
|---|---|---|
| 字符串数学模拟 | 压缩、翻转、循环移位、括号匹配 | LeetCode 443 / 151 |
| 哈希表 + 自定义排序 | 频率排序、Top K、同字母异分词 | LeetCode 347 / 49 |
| 动态规划 | 编辑距离、背包问题、最长公共子序列 | LeetCode 72 / 1143 |
| 前缀和 + 有序容器 | 子数组和类问题、区间查询 | LeetCode 560 / 974 |
把这些基础模型吃透,再刷 10-20 道同类变种题,笔试的“保底分”基本就能拿到。剩下那些压轴题,更多考察临场数学推导能力,遇到没见过的新题也不要慌,先暴力再优化,拿到部分分就是胜利。
4.4 平台操作规范与远程笔试注意事项
这里单独提一下在线笔试本身的“非算法风险”。我记得当年这场笔试要求提前 15 分钟进入系统,做设备检测,包括摄像头、麦克风、屏幕共享权限。有时候插件装不上或者浏览器版本不兼容,会直接影响考试,这种事故完全不值得。
以下几点是我自己多次笔试总结出来的,强烈建议收藏:
- 提前一天测试摄像头和屏幕共享,不要等开考前 5 分钟才慌
- 浏览器建议用 Chrome 或官方指定的版本,关掉所有无关插件
- 在线笔试期间不能切换出浏览器,否则容易被判作弊,这个务必注意
- 准备一张草稿纸和笔,不是所有题都适合直接在编辑器里想
- 如果家里网络不稳定,能去学校机房或网吧就尽量去,掉线比不会做更冤
5. 常见错误与高频翻车点汇总
5.1 笔试现场最容易犯的 5 类错误
我前后参加了大概 10 场校招在线笔试,见过也踩过不少坑,这里总结一个“高频翻车清单”:
| 错误类型 | 典型案例 | 解决思路 |
|---|---|---|
| 读题不清 | 忽略了“压缩后长度必须小于原串”的返回条件 | 动笔前先把题目翻译成自己的话,确认输出要求 |
| 边界遗漏 | 字符串为空、数组长度为 1 时直接越界 | 写完代码先跑边界用例再提交 |
| 时间分配失衡 | 最后一题死磕 40 分钟,前两题草草提交 | 先快速浏览 4 题,按性价比分配时间 |
| 环境不熟悉 | 编辑器没有自动补全,语法报错定位慢 | 提前熟悉牛客网等平台的在线编辑器 |
| 过度自信 | 代码一跑样例通过就直接提交,没有测试边界 | 本地或脑子里跑 3-4 组用例再交 |
5.2 正确率 vs 速度的取舍
在线笔试有两种典型选手:一种是“求稳型”,每道题都要跑测试用例、反复检查,最后可能只做完 2 道;另一种是“求快型”,写完就交,速度很快但错误率高,AC 率上不去。
说实话,这两种都容易挂。校招笔试的判分不是“做对两道就给两道满分”,而是“每道题按通过用例比例给分”。所以最优策略是:先保证简单题 100% 正确,中等题争取 100%,压轴题拿到部分分。为了达到这个目标,你其实不需要特别快的速度,而是需要稳定的准确率和良好的时间分配能力。
5.3 复盘方法:笔试结束后怎么最大化利用这次经历
笔试结束后,别光看分数。分数只是结果,更重要的复盘是:哪些题是因为知识盲区没做出来,哪些题是因为马虎丢分,哪些题是时间不够。我自己每次笔试后会做一个简单表格,记录每道题的掌握情况、失误原因、改进方向,然后对照刷题列表补漏。
这种复盘方法看起来麻烦,实际坚持下来收益很大。笔试的意义不只是“拿 offer”,它也是对你当前算法水平的一次客观体检。校招周期长,前期笔试暴露的问题,完全有时间在面试前补齐,关键是你愿不愿意花这个功夫。
6. 经验小结:什么是真正有效的刷题方式
最后分享一点我个人对“刷题”这件事的体会。
有人喜欢一天刷 10 道题,追求数量上的快感,但这样往往只是“见题型,背套路”,题目稍微变形就卡住。我更推荐“每题吃透”的方式:拿到一道题,先自己想思路,想不出再参考答案,理解后再关掉答案自己写一遍,最后把这道题的考点整理到自己的笔记里。这个流程走完,才算真正掌握了一道题。
这次的 4 道题里,第 4 题给我的启发最大。它是那种“暴力解法谁都能想到,但最优解需要数学推导和数据结构支撑”的题目。后来我在准备面试时,专门把所有“前缀和取模”相关的题目都刷了一遍,比如连续子数组和、可被 K 整除的子数组等。刷题要刷同类变种,不要只刷孤立的题号。只有把一道题背后的数学模型吃透,遇到新题时才能举一反三。
希望这篇复盘对正在准备校招的你有所帮助。如果你们在笔试中碰到其他有意思的题目,欢迎回来一起讨论。