☰
大厂暑期实习机试复盘:题型套路、时间分配与避坑指南
2026/10/17 3:05:51 网站建设 项目流程

2021届暑期实习的机试,让很多人第一次真切感受到大厂筛选的残酷。简历能过、测评能过,等到在线编程界面弹出来的那一刻,剩下的就全凭代码说话。我身边不少准备得挺认真的同学,恰恰挂在机试这一关,不是不会做,而是没按机试的规则去练。这里想把当年那家头部通信技术企业实习招聘机试的完整过程复盘一遍:题型、思路、代码、踩坑,一个不落。

这篇文章对两类人最有价值。一类是正在准备暑期实习、马上要踏进机试考场的同学;另一类是刚开始刷算法题、想知道大厂机试到底考什么的小白。看完你至少能明确三件事:机试的底层规则是什么、考场上怎么分配时间、剩下这段时间到底该练什么。我尽量把话说得直接一些,毕竟机试这件事,骗自己没用,判题器只看结果。

哪怕你已经刷了不少题,我还是建议先看第一部分。因为机试和平时刷题完全是两套玩法,搞不清规则,刷得再多也容易在考场上翻车。

1. 开始刷题之前,先搞清机试的三个底层规则

1.1 考试形式与判题规则:两个小时三道题,到底怎么给分

先说最实际的:这场机试是三道编程题,限时两个半小时,部分批次缩到两小时。题目难度从简单到中等再到偏难,覆盖模拟、动态规划、搜索这几类经典题型。语言方面基本不限,C/C++、Java、Python、Go都能用,但强烈建议你用平时最熟练的那门语言,不要在考场上用一门“想练练”的语言。

判题规则需要特别留意。它不像我们平时刷题平台那样提交后立刻能看到全部用例细节,通常是按通过测试用例的比例给分的。也就是说,一道题你只过了简单用例、复杂用例挂了,不会直接得零分,判题系统会按过掉的用例比例给分。很多同学不知道这一点,在一道难题上死磕,结果后面简单题都没时间写完,这是最亏的。

另外一个隐形规则是:本地IDE随便用,代码写完再粘贴到网页编辑器提交。但有些批次有反作弊机制,例如切换页面会弹提醒。所以备考阶段就要养成一个习惯:不要依赖编辑器的自动补全和在线提示,尽量做到能在白板环境下裸写代码。真到了考场,哪怕编辑器简陋,你也不慌。

1.2 题目难度分布与时间分配:先保二,再争三

根据我对这类机试的观察,三道题的分布通常很有规律,我整理成了下面这张表:

题号常见题型建议耗时目标
第一题字符串处理、数组模拟15-25分钟满分,所有用例必须全过
第二题动态规划、前缀和、滑动窗口30-40分钟拿到80%以上用例分数
第三题图搜索、复杂模拟、状态压缩BFS剩余时间先写暴力,拿部分分,不强求AC

时间分配上,我的建议是:拿到题先花几分钟把三道题都扫一遍,对难度有个整体判断,不要从第一题开始就埋头写。第一题相对简单,但不能掉以轻心,它往往是藏坑最深的题。第二题是整场考试的胜负手,正常复习过DP的同学都能啃下来。第三题如果做了10分钟还没有任何思路,果断先写一个暴力版本,把能拿的部分分拿到手里,再回头优化。

记住一个核心策略:机试的目标不是做完美全部题目,而是拿尽量高的总分。一道AC的难题,不如两道半AC的中等题实际。这一点和真正的工作也很像,先把靠谱的功能交付掉,再去挑战难点。

1.3 机试真正筛掉的是什么人:不是不会算法的人

面试过不少候选人之后,我越来越确定一件事:机试筛掉的不是不会算法的人,而是读题不仔细、编码习惯差、边界意识薄弱的人。

读题不仔细的例子太多了。题目明明说“连续出现一次的字符不输出次数”,有些人没看到,直接照着“字符+次数”的默认逻辑写,样例一过就觉得稳了,结果隐藏用例大面积报错。编码习惯差体现在变量命名随意、只考虑主路径、完全没有防御性判断。边界意识薄弱则是最常见的翻车点:空输入没处理、数组只有一个元素没考虑、大数乘法溢出没换long long、搜索题目起点和终点相同直接返回-1。

机试就像驾考科目二,不考漂移,只考你在标准场景下能不能不压线。算法竞赛里的那些奇技淫巧,机试基本用不上,但基本的边界处理、复杂度估算、代码整洁度,每一个都直接决定你的分数。所以后续备考的核心并不是“多刷题”,而是“按考试规则刷题”。

2. 真题复盘:三道题的读题、思路与完整代码

先把丑话说在前面:下面这三道题不是官方原题的原样复刻,而是我把同届考生的反馈和自己的机试回忆整理到一起,还原出来的“同考点、同难度、同风格”版本。机试的具体测试用例已经记不全了,但这三题的题型在历年机试里反复出现,覆盖了模拟、动态规划、搜索这三类最常见的考察方向。把这三题吃透,你面对的就不是陌生题型,而是换了一层皮的熟面孔。

2.1 第一题:字符串压缩,考的是读题和收尾

题目要求如下:给定一个字符串,把其中连续重复出现的字符进行压缩,输出“字符+连续出现次数”的形式,但连续只出现1次的字符不输出次数。

示例输入:aaabccccaa
示例输出:a3bc4a2

先别急着写。这道题虽然不难,但有两个明显的坑。第一个坑是“连续出现一次不输出次数”,所以b后面不应该跟1。第二个坑藏在代码结构里:很多同学习惯边遍历边拼接,结果遍历结束后,最后一组连续字符因为还没遇到“变化点”而漏结算,导致输出比实际少一段。

正确的解法是双指针扫描。用外层循环控制当前字符的起始位置i,内层循环找连续相同字符的结束位置j,j-i就是连续次数。找到后立即结算并移动i到j,这样就从结构上避免了漏掉末尾段的问题。时间复杂度O(n),空间复杂度O(n),完全满足数据规模要求。

这道题给我们的真正启示是:简单题里最容易丢分的地方,往往不是算法本身,而是你太自信、跳过边界思考直接开写。机试第一题,一定要慢读题、快编码。

2.2 第二题:最大连续子数组乘积,考的是动态规划的符号处理

第二题是经典动态规划题型,题目描述如下:给定一个长度为n的整数数组,元素包含正数、负数和0,求连续子数组的最大乘积,输出这个最大乘积。

示例1输入:2 3 -2 4
示例1输出:6
因为子数组[2,3]的乘积是6,比[2,3,-2,4]的乘积-48更大。

示例2输入:-2 0 -1
示例2输出:0
因为子数组[-2,0]或[0]的乘积都是0,都比单个-2大。

这道题如果n到10万级别,暴力枚举起点和终点是O(n²),直接超时。核心思路是用动态规划:定义dp_max[i]是以第i个元素结尾的连续子数组乘积最大值,dp_min[i]是以第i个元素结尾的连续子数组乘积最小值。为什么还要记录最小值?因为负负得正。当前元素如果是负数,之前的最小乘积乘上它,反而可能变成最大乘积。如果不维护最小值,就会漏掉这类情况。

转移的时候,我们需要考虑三种可能性:当前元素单独作为一个新的子数组,之前最大乘积乘上当前元素,之前最小乘积乘上当前元素。三者取最大作为新的dp_max,三者取最小作为新的dp_min。这个转移式虽然简单,但它是“最大子段积”这类题的标准模板,值得背熟。

2.3 第三题:安全距离迷宫,考的是双BFS的转译能力

第三题是整场机试里区分度最高的一道,它把“多源BFS”和“带约束最短路径”结合在了一起,题面如下:

给定一个m行n列的网格地图,0表示空地,1表示障碍物,2表示怪物。给定安全距离k,每一步可以上下左右移动一格。规定路径经过的任意位置到最近怪物的步数必须不小于k,起点和终点也不例外。求从起点到终点的最短步数,无法到达则输出-1。

示例:
3 3 1
0 0 0
0 2 0
0 0 0
起点(0,0),终点(2,2),输出4。

这个题拆解成两步就清晰了。第一步,先求出每个格子到最近怪物的距离。网格边权是1,所以只要把所有怪物同时作为起点做一次多源BFS,就能一次性算出每个格子的“危险距离”。第二步,再从起点做一次普通BFS,但移动时要加一个限制:目标格子的危险距离必须大于等于k。这样找出来的最短路径,天然满足安全约束。

能想到用多源BFS是关键分水岭。如果每个怪物都单独做一次BFS并取最小值,复杂度是怪物数量乘以网格面积,最坏情况下直接炸掉。而多源BFS相当于把所有怪物同时放进队列,每个格子只被第一次访问时记录距离,整体复杂度是O(m×n),是这道题的标准最优解。这类“先预处理,再跑约束路径”的题,在机试里越来越常见,值得单独做专题训练。

3. 代码实现与易错点:完全照着写就能避坑

3.1 第一题完整代码:双指针写法最稳

下面是第一题的完整Python实现,我建议你直接照这个结构写:

def compress(s: str) -> str: if not s: return "" res = [] n = len(s) i = 0 while i < n: j = i while j < n and s[j] == s[i]: j += 1 cnt = j - i res.append(s[i]) if cnt > 1: res.append(str(cnt)) i = j return "".join(res)

核心要点有几个。第一,空字符串直接返回空串,这是最容易被忽略的边界条件。第二,内层while循环找“下一个不同字符”的位置,结算完之后把i直接移动到j,而不是i++,这样可以跳过一整段重复字符,效率更高。第三,用列表收集字符,最后join成字符串,不要在循环里反复做字符串拼接。字符串是不可变对象,每拼一次都会生成新对象,在10万级字符串上会有明显的性能损耗。

测试时把这几种情况全部跑一遍:空串、单个字符“a”、全部相同“aaaa”、全部不同“abcd”、末尾是重复段的“aaabccccaa”。特别是最后一种,最容易暴露漏结算的bug。

3.2 第二题完整代码:三路取最大,不用脑补符号

第二题的完整实现如下,核心是维护两个状态变量:

def max_product(nums): if not nums: return 0 ans = nums[0] cur_max = nums[0] cur_min = nums[0] for num in nums[1:]: old_max = cur_max old_min = cur_min cur_max = max(num, old_max * num, old_min * num) cur_min = min(num, old_max * num, old_min * num) ans = max(ans, cur_max) return ans

为什么这一步要先保存old_max和old_min?因为计算新的cur_max和cur_min时,需要用到前一个位置的值,如果直接在原变量上修改,后面的计算就会污染。这种“滚动变量”的写法,在动态规划里非常常见。

逐个解释三路候选的含义。num单独成段,处理的是“前面乘积不如当前元素自己大”的情况,比如之前累计为0,当前是正数。old_max * num,是正正得正,继承之前最大值继续累乘。old_min * num,专门处理负负得正。这三路取最大,逻辑上无懈可击,比“遇到负数交换max和min”的写法更不容易写错,也不需要额外讨论符号。

边界测试建议覆盖:单元素数组[-2]、全负数数组[-2,-3,-4]、含0数组[-2,0,-1]、正负交替数组[2,3,-2,4]。如果用的是C++,变量类型记得用long long,因为乘积容易超过int范围。Python因为整数不溢出,这个坑天然不存在。

3.3 第三题完整代码:两个BFS模板化

第三题完整实现如下,分成两个函数,结构非常清晰:

from collections import deque def multi_source_bfs(grid, monsters): m, n = len(grid), len(grid[0]) INF = 10 ** 9 dist = [[INF] * n for _ in range(m)] q = deque() for x, y in monsters: dist[x][y] = 0 q.append((x, y)) dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)] while q: x, y = q.popleft() for dx, dy in dirs: nx, ny = x + dx, y + dy if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] != 1 and dist[nx][ny] == INF: dist[nx][ny] = dist[x][y] + 1 q.append((nx, ny)) return dist def shortest_safe_path(grid, sx, sy, tx, ty, k): m, n = len(grid), len(grid[0]) monsters = [(i, j) for i in range(m) for j in range(n) if grid[i][j] == 2] safe_dist = multi_source_bfs(grid, monsters) if safe_dist[sx][sy] < k or safe_dist[tx][ty] < k: return -1 q = deque([(sx, sy, 0)]) visited = [[False] * n for _ in range(m)] visited[sx][sy] = True dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)] while q: x, y, step = q.popleft() if x == tx and y == ty: return step for dx, dy in dirs: nx, ny = x + dx, y + dy if 0 <= nx < m and 0 <= ny < n and not visited[nx][ny] and grid[nx][ny] != 1 and safe_dist[nx][ny] >= k: visited[nx][ny] = True q.append((nx, ny, step + 1)) return -1

第一个函数注意三件事。dist数组初始化为一个大数INF,用来表示“还没被扩散到”。队列初始把所有怪物放进去,所有怪物同时向外扩散。每次弹出一个格子,向四个方向扩展,遇到障碍物跳过,遇到已经算过的格子跳过。由于BFS逐层扩展的特性,第一次访问到某个格子时记录的距离,就是它到最近怪物的最短步数。

第二个函数是普通的带约束BFS。先检查起点和终点的安全距离,如果起点或终点本身就是危险区域,直接返回-1。visited数组务必在入队时立刻标为True,防止同一个格子被重复加入队列。步数step跟着队列节点一起走,访问到终点时直接返回。队列空了还没到终点,说明无可达路径,返回-1。

另外注意一个细节:题面如果规定k大于等于1,那么怪物所在格子的危险距离是0,永远不会走进去,这符合直觉。但如果k=0,上述代码也能正确工作,因为0>=0成立。这样写包容性更好。

4. 在线判题环境与本地IDE的配合心得

4.1 输入解析模板:先读进来,再统一处理

机试最磨人的往往不是解题思路,而是输入解析。平台的测试用例可能是多行、带多个空格、或者第一行给n第二行给数组,格式稍有变化就容易在解析上浪费大量时间。我的习惯是统一用“全部读进来,再用迭代器取数”的方式处理,基本能应对九成以上的题型。

import sys def main(): data = sys.stdin.read().split() if not data: return it = iter(data) n = int(next(it)) nums = [int(next(it)) for _ in range(n)] # 后续逻辑直接基于 nums if __name__ == "__main__": main()

如果是C++,建议这样起步:

#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<int> nums(n); for (int i = 0; i < n; i++) cin >> nums[i]; return 0; }

C++里关闭同步那两行不是玄学,是为了避免cin和stdio同步机制带来的性能损耗。当输入规模达到10万级的时候,同步开销会让程序明显变慢,机试环境里可能因此卡掉某些刁钻用例。提前把模板写好,上考场直接复制,能省出不少时间。

4.2 提交前自测用例:把最容易翻车的几种输入测一遍

我踩过最大的坑是:本地用例全过,提交后用例通过率却只有60%,而且看不到错在哪里。后来总结出一个固定的自测套路,每次提交前都按下面的表过一遍:

题型必测用例检查目的
字符串类空串、单字符、所有字符相同、末尾连续段防漏结算、防越界
数组类单元素、全负数、含0、最大值最小值防初始化错误、防精度溢出
动态规划类空数组、单元素、全负数、正负交替防状态初始化错误
搜索类起点即终点、无解、障碍包围、起点终点紧邻防visited漏标、防返回错误值

这个方法比“多测几个随机用例”靠谱得多。隐藏用例最喜欢测的就是边界情况,你主动把所有边界提前跑一遍,提交的时候会安心很多。真机试不像平时刷题能立刻看到判题结果,你必须自己扮演判题系统。

4.3 数据范围与复杂度速查:三分钟判断解法能不能过

很多同学看到题目后不知道用什么算法,本质是不会根据数据范围反推复杂度。记住下面这张表,思路就能快速收敛:

数据规模可接受复杂度代表解法
n ≤ 20O(2ⁿ) 或 O(n!)状态压缩、暴力枚举
n ≤ 1000O(n²)双重循环DP、前缀和暴力
n ≤ 10⁵O(n) 或 O(n log n)双指针、单调栈、二分、线段树
n ≤ 10⁹O(log n) 或 O(1)二分答案、矩阵快速幂、数学公式

拿到题目先看数据范围。如果n是10万,你脑子里就不应该出现O(n²)解法。如果n是1000,你可以放心用两层循环。如果n是10的9次方,只能往二分答案或数学方向想。这套判断方法练熟之后,能帮你快速排除掉错误方向,在考场上非常提气。

5. 从刷题到上岸:一套可执行的备考路线

5.1 按专题刷,按模板记,别拼题量

备考机试最忌讳的就是东刷一道西刷一道。算法题的套路是有限的,按专题去攻,效率远高于盲目拼数量。建议按下面这个顺序过一遍:

  • 字符串与双指针:压缩、翻转、括号匹配、回文子串
  • 哈希表:两数之和、字母异位词分组、最长连续序列
  • 排序与二分:归并排序、快速排序、lower_bound、特殊二分
  • 链表:反转、合并有序链表、环形链表检测
  • 栈与队列:单调栈、优先队列、最小栈
  • 动态规划:子序列问题、背包问题、区间DP、股票系列
  • 树:前中后序遍历、层序遍历、最近公共祖先
  • 图:最短路、拓扑排序、并查集、多源BFS

每个专题不需要贪多,选五道经典题吃透就够了。但“吃透”的标准是:合上答案能独立写出来,并且能说出每个易错点在哪里。推荐准备一个错题本,记录每个专题里自己最容易犯的错。比如我的本子上就写着:字符串题必查末尾、DP初始化不能用0、BFS的visited必须入队即标记。考前翻一遍,比临时刷题安心得多。

5.2 每周一次模拟考,复制真实节奏

刷题刷到一定量以后,必须进入“模拟考”环节。每周找整块时间,严格按机试规则练一次:两道半小时、三道题、本地IDE加网页提交的流程。模拟时注意以下几点:

  • 10分钟扫完全部题目,给每道题标难度
  • 第一题最多25分钟,超时先放一放
  • 第二题40分钟,卡住就写暴力版本拿部分分
  • 第三题最后处理,优先保前两题的正确率
  • 最后留20分钟统一检查输入输出格式和边界用例

我见过不少同学刷题刷得很猛,但一到限时环境下就手忙脚乱。模拟考的核心价值,就是让你适应“时间压力下的决策”。真实机试只有一次机会,平时不多练几次,考场上心态很容易崩。

5.3 机试不是终点,保留代码用于复盘

机试通过之后,招聘流程还没结束,后面还有技术面、综合面。机试中的题目和你的解题思路,经常会被面试官拿来追问。比如你第三题用了双BFS,面试官可能会问:多源BFS和多次单源BFS的区别是什么?复杂度分别是多少?能不能改成A*算法?所以机试结束后,不要急着清空草稿,把自己写的代码和思路整理成文档,这是后面面试复盘的好素材。

从求职的角度看,机试更像是招聘流程里的一个“技术体检报告”。它直观地反映了一个人的代码功底、边界意识和算法基本功。用心治一治体检报告上的问题,受益的不只是一场考试,更是后续每一轮技术面试。

最后说点个人体会。那场机试之前,我总觉得自己刷得不够多,不敢上场。直到考完才发现,真正决定分数的不是题量,而是把常见题型的套路和边界条件吃透。另一个让我受益很大的习惯是:每道题写完以后,先跑三种输入——空输入、单元素、极端值。八成以上的翻车,都藏在这三种情况里。备考最后两周,少看新题,多复盘旧题,把你自己的错题本从头翻一遍,比什么都管用。希望这篇复盘能帮你在机试考场上少踩几个坑,一次AC。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询