机试是很多技术岗求职者绕不开的第一道硬门槛。我身边见过不少同学,LeetCode刷了两三百题,平时写代码也挺顺,结果一到机试就翻车——不是超时,就是没读懂输入格式,甚至有人连第一道题都因为输出格式不对拿了零分。问题往往不在代码能力,而在于对机试本身缺少系统认知:考什么、怎么练、现场怎么排错,这些都是可以提前准备的。
这篇内容就是一份“题目汇总+备考路标”性质的整理。我把机试常见的题型分布、高频考点、刷题路径、现场避坑细节、可直接套用的代码模板,以及几道经典题的完整精解都过一遍,帮你把零散刷题经验归纳成一张能复用的地图。无论你是刚开始准备的萌新,还是已经刷过一轮想冲刺的求职者,这份梳理都值得花半小时认真看一看。
1. 机试到底考什么:一张题型地图先看清全局
1.1 编程题为主,核心考察三类能力
机试一般以在线编程题为主体,时长通常在两小时左右,题量从两道到四道不等,难度呈阶梯式上升。第一道往往是基础题,考察字符串处理、数组操作或简单模拟,属于送分题但不容有失;中间那道会涉及常见数据结构或基础算法,比如哈希表、双指针、二分查找;压轴题则偏向动态规划、图遍历或贪心策略的综合运用。
归纳起来,机试考察的是三类能力:第一类是算法与数据结构基本功,这是区分度的主要来源;第二类是代码实现的熟练度,能不能在有限时间内把思路完整写成无bug的代码;第三类是工程细节意识,包括输入输出格式、边界条件、时间复杂度估算,这些细节恰恰是很多人丢分的重灾区。
1.2 分值分布与过线逻辑
分值分布一般也是阶梯式的,第一题占比较低,中间题中等,压轴题权重最高。过线的逻辑有两个关键点:
- 总分过线是底线,但前面的简单题是基石,压轴题做不出来的情况下,稳定拿满前两题也往往能过。
- 部分场景会按通过率加权,即部分通过的用例也会给分,所以即使思路不完整,也要尽量写出暴力解,多过几个测试点就多几分。
1.3 语言与环境:提前确认这四件事
很多同学忽略环境差异,到考场上才发现自己熟悉的操作在机试系统里行不通。建议考前务必确认四件事:
- 编译器版本:比如Python是3.x还是某个具体小版本,C++是否支持C++17,这直接决定你能不能使用某些语法特性。
- 标准输入输出方式:机试一般要求从标准输入读取数据,用标准输出打印结果,不推荐读写文件;个别特殊场景需要读文件,务必提前看考试说明。
- 第三方库限制:有的环境仅提供标准库,不支持外部包装库,刷题时尽量别依赖某些冷门库函数。
- 代码提交模式:有的系统是“只贴函数”,有的要求写完整程序,后者必须在代码里自己处理输入解析,差别很大。
2. 高频考点逐个拆:数据结构和算法的真实出题比重
2.1 数据结构类考题怎么出
数组与哈希表是出现频率最高的组合。数组题往往不是单纯遍历,而是围绕双指针、滑动窗口、前缀和这些技巧展开。比如“两数之和”是哈希表的入门题,进阶版本包括“三数之和”“最长无重复子串”这类滑动窗口变形。哈希表的核心价值是把查找时间从O(n)降为O(1),遇到需要频繁判断“某个值是否出现过”的题目,优先考虑它。
链表也经常出现,考察点集中在反转链表、合并有序链表、找环入口。这类题对指针操作要求较高,建议提前把迭代反转和递归反转两种写法都练熟,因为机试环境调试链表不方便,一步到位写出正确代码很重要。
栈与队列是模拟题的好搭档,典型如括号匹配、表达式求值、用栈实现队列。单调栈和单调队列属于进阶考点,但题目一旦出现,往往是中等以上难度,用来拉区分度。
2.2 算法思维类考题如何分析
排序与二分查找是性价比极高的考点。排序不只是调用库函数,很多题需要你手写排序逻辑的变体,更常见的是利用排序后数组的有序性配合双指针解题。二分查找则要重点掌握“在有序数组中找边界”的写法,也就是找左边界和右边界的模板,这比单纯找目标值要难一个层次,也是高频考点。
动态规划是压轴题的常客。高频类型包括:一维DP(如爬楼梯、打家劫舍)、二维DP(如不同路径、最小路径和)、背包类问题(0-1背包及其变体)。动态规划的难点在于状态定义和转移方程,建议刷题时先想清楚“dp[i]代表什么”,再写循环,能有效避免思路混乱。
图论中的DFS/BFS是另一座大山。网格类问题(岛屿数量、迷宫最短路径)实际上是图的隐式表达,DFS适合统计连通块,BFS适合求最短步数。这两个模板一定要背到滚瓜烂熟,因为题目稍作变形就能变成中等题。拓扑排序也是一个值得掌握的考点,尤其是有向图判断是否有环的场景。
2.3 字符串与模拟题:最容易被低估的两个板块
字符串处理题看起来简单,但实际不简单。反转、切割、子串查找是基本功,进阶会考察KMP思想(通常用库函数也能过,但理解原理有助于处理边界)、字符串与数字相互转换、回文串判断与扩展。一个常见的陷阱是:Python中字符串不可变,很多操作会产生新对象,不注意会导致额外空间消耗。
模拟题则是“读懂题意就赢了一半”,题干往往很长,描述一个完整流程,要求你按规则一步一步实现。这类题不考高深算法,考的是把自然语言描述转换成代码的能力。我的建议是先把规则用注释拆分成步骤,再逐步编码,不要试图一口吃成胖子。
2.4 考点频率与优先级参考
| 考点分类 | 代表题型 | 出现频率 | 优先级 |
|---|---|---|---|
| 数组/哈希表 | 两数之和、滑动窗口 | 极高 | 必练 |
| 字符串处理 | 反转、子串、括号匹配 | 高 | 必练 |
| 排序/二分 | 边界查找、合并区间 | 高 | 必练 |
| 动态规划 | 背包、路径、打家劫舍类 | 中高 | 重点练 |
| 栈/队列 | 单调栈、表达式求值 | 中 | 重点练 |
| DFS/BFS | 岛屿、迷宫、拓扑 | 中高 | 重点练 |
| 贪心算法 | 区间调度、任务分配 | 中 | 掌握套路 |
| 数学/位运算 | 质数、幂运算、异或 | 中低 | 按需练习 |
优先级的判断标准很简单:频率高且代码量短的知识点可以花少量时间拿到稳定分数,像数组、哈希、字符串;频率高且难度大的考点(动态规划、图)需要投入更多时间但回报也高;低频冷门考点不必过度纠结。
3. 从零到过线的刷题路径:我的训练节奏与复盘方法
3.1 三阶段训练法:打基础、刷专项、模考冲刺
我自己比较推荐把备考分成三个阶段,节奏清晰,不容易迷茫。
第一阶段是打基础,约一到两周,目标是把常见数据结构的基本操作写熟练。不要上来就刷难题,先把数组、链表、栈、队列、哈希表、树的增删改查遍历写一遍,每种结构至少手写三遍以上,直到不假思索就能写出来。这个阶段可以按“分类刷简单题”的方式进行,每天保证三道以上通过。
第二阶段是刷专项,大约三到四周,按考点逐个击破。比如这周专攻二分查找,下周专攻动态规划。每个专项先看经典模板,再刷中等题,最后回头做错题。这个阶段的关键是“一道题吃透胜过十道题略过”,遇到不会的题,先独立思考二十分钟,再看解法,然后合上答案自己重写一遍。
第三阶段是模考冲刺,考前一到两周,严格按机试环境模拟。定时两小时,用系统随机抽题或往年真题,整个过程中不查资料、不中断,模拟真实考场状态。模考后做完整复盘,找出是读题慢、思路慢还是bug修得慢。
3.2 怎么复盘才不白刷
复盘比刷题本身更重要。很多同学刷题只追求“AC”,提交通过就扔一边,这种习惯导致同样的坑下次继续踩。我的复盘方法是,每道题提交通过后,再问自己五个问题:
- 这道题的核心考点是什么?用到哪种数据结构或算法?
- 我的解法和标准解法的差异在哪?有没有更优的时间或空间复杂度?
- 我在哪里卡住了?是状态定义不清,还是边界条件没想到,还是对某个库函数不熟悉?
- 如果换一个输入,我的代码会不会越界或超时?
- 这类题下次再遇到,第一步应该怎么思考?
回答完这五个问题,才算真正刷完一道题。复盘记录不用很长,几句话写清楚卡点和解法核心即可,重点是提取出“可迁移的套路”。
3.3 错题本的正确用法
错题本不是把题目和代码抄一遍就完事,而是要按“题型 + 卡点 + 模板”的结构来记录。举个例子,“动态规划 | 忘记初始化二维dp数组的第一行第一列 | 需要先处理边界行,再用递推公式”。这样的记录能让你考前快速过一遍自己的弱点。
整理错题本时,建议按考点分标签,而不是按时间顺序。这样临考前可以只复习“最容易出错”的标签,效率比从头翻一遍高很多。另外,错题本里要留一栏备注“下次做题提醒”,比如“看到最值问题先想DP”“看到连通性、岛屿先想BFS/DFS”“看到合并区间先想排序”。
4. 机试现场最容易翻车的细节:六个实战雷区
4.1 输入输出格式:第一道题就挂在这里的人最多
机试最常见的翻车点不是算法,而是输入输出格式。我看到太多人栽在这种地方:题目要求读取一行包含多个整数的字符串,却默认按空格分割逐项读取,结果遇到逗号分隔就懵了;还有题目要求输出结果保留两位小数,直接打印浮点数导致格式错误。
解决这个问题没有捷径,只能养成一个习惯:写代码前先用注释把输入输出格式写出来。比如:
# 输入:第一行是n,第二行是n个整数,用空格分隔 # 输出:按从小到大排序后的结果,每行一个把格式写清楚再动手,能避免八成格式错误。另外,处理输入时推荐按行读取再分割,而不是直接使用某些格式化输入函数,因为机试的输入行数、列数往往不固定,按行读取更灵活。
4.2 超时与内存超限的排查套路
超时是机试第二常见的翻车点。遇到超时时,不要盲目优化代码,先看题目给的数据范围:
- 如果n在10^3以内,O(n^2)暴力解通常没问题。
- 如果n在10^5以上,O(n^2)必然超时,需要换O(n log n)或O(n)的解法。
- 如果n在10^6以上,要考虑O(n)甚至O(log n)级别的算法。
内存超限相对少见,但也要注意不要无谓地创建大数组、使用过深的递归。尤其是DFS递归深度过大时,可能因为栈溢出而报错,这时要把递归改成显式栈的迭代写法。
4.3 边界条件:空输入、越界、最大值
边界条件是机试评分的隐藏杀手。一道看似简单的题,如果没处理空数组、单个元素、负数、极大值,可能在隐藏用例上直接挂掉。拿“求数组最大值”来说,空数组应该返回什么?很多系统会用特定值表示错误,需要看题目约定。
一个实用的自测习惯是:写完代码后,立刻在脑中跑几个特殊用例——空输入、最小输入、最大输入、重复元素、负值。不用真的跑全部用例,但要在心里过一遍代码在特殊场景下的行为。
4.4 编译器版本与语法差异
机试系统和你本地的开发环境往往不是同一个版本。比如Python 2和Python 3的print语法完全不同,C++的auto、unordered_map在某些老旧编译器下可能不可用。考前最好用与机试相同的环境做几次模拟,确认语法兼容性。
一个惨痛教训是,有人用了Python 3的f-string语法,结果机试环境只支持Python 3.5,直接编译失败。所以我的建议是,平时刷题就尽量避免使用太新的语法特性,尤其是那些“优雅但冷门”的写法,考试时用最稳妥的写法。
4.5 多组测试用例与自测习惯
机试系统通常用多个测试用例来评判你的代码,所以你的程序必须能处理一次运行内连续处理多组输入的情况,而不是只处理一组就退出。正确处理方式是循环读取输入,直到文件结束或读不到数据,而不是只读取一次。
自测时也一样,不要只测题目给的示例,要自己构造几组边界数据。我见过有人自己用示例数据测就提交,结果隐藏用例直接超时或越界。正确的做法是,提交前留出三到五分钟,专门构造特殊用例自测。
4.6 心态与时间分配:先易后难是铁律
机试的时间分配需要一点策略。拿到题目后,先花三到五分钟快速浏览全部题目,搞清楚每题的大致难度,然后按“简单→中等→困难”的顺序做题。如果某道题卡了二十分钟还没有头绪,果断跳过,先保证其他题拿到分。
考场上最可惜的情况是:把大把时间耗在压轴题上,导致前面的简单题没时间写完。记住一句话:机试比的是总分,不是谁能做出压轴题。
5. 通用解题模板:直接可用的代码骨架
5.1 快速输入输出模板
机试的输入读取一定要写成可复用的模板,不要在考场上临时拼凑。Python推荐写法:
import sys def solve(): # data = sys.stdin.read().split() # 按行读取并去除首尾空格 lines = [line.strip() for line in sys.stdin.readlines() if line.strip()] if not lines: return n = int(lines[0]) arr = list(map(int, lines[1].split())) # 处理逻辑... print(result) if __name__ == "__main__": solve()一次性读取全部输入再按需解析,比一行行调用input()更高效,尤其在输入量大的时候,readlines方式能明显减少IO时间。
5.2 二分查找通用模板
二分查找的难点是边界处理,尤其找左边界和右边界时容易死循环。推荐统一使用“左闭右开”的写法:
def lower_bound(nums, target): left, right = 0, len(nums) # 左闭右开 while left < right: mid = (left + right) // 2 if nums[mid] < target: left = mid + 1 else: right = mid return left # 第一个 >= target 的位置这个模板的优点是,区间定义清晰,退出条件明确,改成“找第一个大于target”只需要把nums[mid] < target改成nums[mid] <= target。记熟一个模板,比在考场上临时推导边界要稳得多。
5.3 深度优先搜索模板
DFS是网格类题目的基础模板。以“岛屿数量”为例,核心写法如下:
def num_islands(grid): if not grid: return 0 m, n = len(grid), len(grid[0]) count = 0 def dfs(i, j): if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != '1': return grid[i][j] = '0' # 标记已访问 dfs(i - 1, j) dfs(i + 1, j) dfs(i, j - 1) dfs(i, j + 1) for i in range(m): for j in range(n): if grid[i][j] == '1': count += 1 dfs(i, j) return count这里的核心技巧是“原地标记”,直接把访问过的格子改成0,省去单独的visited数组,既省空间又简洁。
5.4 动态规划基础框架
动态规划写代码时容易乱,我习惯先列清楚四件事再动手:状态定义、初始化、转移方程、遍历顺序。以“最小路径和”为例:
def min_path_sum(grid): m, n = len(grid), len(grid[0]) dp = [[0] * n for _ in range(m)] dp[0][0] = grid[0][0] for i in range(1, m): dp[i][0] = dp[i - 1][0] + grid[i][0] for j in range(1, n): dp[0][j] = dp[0][j - 1] + grid[0][j] for i in range(1, m): for j in range(1, n): dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j] return dp[m - 1][n - 1]边界初始化最容易出错,尤其是二维DP的第一行和第一列,需要单独处理,不能直接套递推公式。
5.5 优先队列与贪心模板
优先队列常用于贪心类题目,比如“合并K个有序链表”“任务调度器”。Python使用heapq模块,模板如下:
import heapq def merge_k_sorted(lists): heap = [] for i, lst in enumerate(lists): if lst: heapq.heappush(heap, (lst[0], i, 0)) result = [] while heap: val, list_idx, elem_idx = heapq.heappop(heap) result.append(val) if elem_idx + 1 < len(lists[list_idx]): heapq.heappush(heap, (lists[list_idx][elem_idx + 1], list_idx, elem_idx + 1)) return result这里之所以要存list_idx和elem_idx,是因为堆里不能只存值,还要记录这个值来自哪个列表的下一个位置,否则后续元素无法访问。这类细节是考场上容易忽略的。
6. 几道经典题的完整精解:从读题到AC的全程拆解
6.1 最长连续序列
题目描述:给定一个未排序的整数数组,找出最长连续序列的长度。要求时间复杂度O(n)。例如[100, 4, 200, 1, 3, 2],最长连续序列是[1, 2, 3, 4],长度为4。
思路分析:这道题表面是数组遍历,但关键在于如何判断“连续序列的起点”。如果我们对每个元素都向左向右扩展找连续值,复杂度是O(n^2),超时。用哈希集合可以让查找时间复杂度降为O(1),然后只在“当前值是一个连续序列的起点”时才开始向后查找,也就是只有当num-1不在集合中时才开始计数,这样每个元素最多被访问两次,整体复杂度O(n)。
def longest_consecutive(nums): num_set = set(nums) longest = 0 for num in num_set: # 只有当 num-1 不存在时,num 才是连续序列的起点 if num - 1 not in num_set: cur = num length = 1 while cur + 1 in num_set: cur += 1 length += 1 longest = max(longest, length) return longest易错点:这个解法依赖“起点判断”,如果漏掉num-1的检查,每个元素都会重复扩展,复杂度退化到O(n^2)。边界条件方面,空数组应返回0,单个元素应返回1。
6.2 任务调度器
题目描述:给定一个字符数组tasks表示CPU需要执行的任务,以及冷却时间n,相同任务之间至少间隔n个单位时间,求完成任务所需的最少时间。例如tasks有3个A和3个B,n=2,则A B A B A B的排法时间是6。
思路分析:核心思路是贪心 + 构造。出现频率最高的任务决定了最少时间的下界。把频率最高的任务看作“骨架”,假设最高频率为max_count,有max_count个这样的任务,则至少需要(max_count - 1) * (n + 1) + 1个单位时间,这个公式的含义是:先排max_count-1轮完整周期,每轮里一个高频任务加n个其他任务,最后再加上最后一个高频任务。
如果其他任务数量很多,足以填满每个冷却槽位,那么总时间就是任务总数。所以答案是上述公式和任务总数的较大值。
def least_interval(tasks, n): freq = {} for t in tasks: freq[t] = freq.get(t, 0) + 1 max_freq = max(freq.values()) max_count = sum(1 for v in freq.values() if v == max_freq) part_count = (max_freq - 1) * (n + 1) + max_count return max(len(tasks), part_count)易错点:如果忽略max_count的处理,会在多个任务同时是最高频时算错时间。比如两个最高频任务各出现3次,n=2,答案是(max_freq - 1) * (n + 1) + 2,而不是+1,因为最后一批必须把两个最高频任务都排进去。
6.3 矩阵中的最短路径(BFS经典)
题目描述:在一个m行n列的0/1矩阵中,求从左上角到右下角的最短路径长度,只能上下左右移动,且路径上只能经过值为0的格子,1是障碍物。
思路分析:最短路优先BFS,因为BFS天然按层扩散,第一次到达终点时的步数就是最短路径。用队列存储坐标,用一个二维数组记录每个格子是否被访问过。注意入队时就要标记已访问,防止同一个格子被多次加入队列造成死循环或效率下降。
from collections import deque def shortest_path(grid): if not grid or grid[0][0] == 1: return -1 m, n = len(grid), len(grid[0]) if grid[m - 1][n - 1] == 1: return -1 q = deque([(0, 0, 1)]) # (row, col, steps) grid[0][0] = 1 # 标记已访问 directions = [(1, 0), (-1, 0), (0, 1), (0, -1)] while q: r, c, step = q.popleft() if r == m - 1 and c == n - 1: return step for dr, dc in directions: nr, nc = r + dr, c + dc if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] == 0: grid[nr][nc] = 1 # 入队前标记访问 q.append((nr, nc, step + 1)) return -1易错点:BFS中“标记访问”的时机很关键。如果出队时才标记,同一个节点可能被多个方向重复加入队列,导致队列膨胀,甚至超时。另外,边界判断要在访问数组元素之前完成,避免下标越界。
6.4 括号生成
题目描述:给定n对括号,生成所有合法的括号组合。例如n=3时,结果是[“((()))”, “(()())”, “(())()”, “()(())”, “()()()”]。
思路分析:这是DFS + 回溯的经典题。合法的括号串需要满足任意前缀中左括号数量不少于右括号数量,且最终左括号数等于右括号数等于n。DFS深度优先搜索构建字符串,每步可以选择加左括号或加右括号,但加右括号时有前置条件:当前右括号数量必须小于左括号数量。
def generate_parenthesis(n): result = [] def backtrack(cur, left, right): if len(cur) == 2 * n: result.append(cur) return if left < n: backtrack(cur + '(', left + 1, right) if right < left: backtrack(cur + ')', left, right + 1) backtrack('', 0, 0) return result易错点:这道题如果忘记right < left这个剪枝条件,会生成大量非法组合,导致结果错误甚至超时。回溯时cur字符串的拼接会创建新对象,在n较大时有一定内存开销,但通常n在20以内问题不大。
7. 考前一周的安排与临场发挥建议
7.1 最后七天的每日清单
考前一周不适合再大量刷新题,重点是回顾和保持手感。我的个人经验是:
- 第7天到第5天:每天做一套完整模考,并整理错题本,把高频考点对应的模板再手写一遍。
- 第4天到第3天:按考点标签复习错题本,重点看反复出错的点,比如二分边界、DP初始化。每天补两道专项题,保持手感就行。
- 第2天:模拟一次完整机试,然后不再刷题,只复习模板代码,尤其是输入输出模板和DFS/BFS模板。
- 第1天:把环境准备好,确认编译器版本、系统环境、打印代码的格式,早点休息。
考前一周最重要的是“不折腾”。不要在最后一天尝试学新算法,也不要熬夜刷题,保持作息和精神状态比多刷几道题更重要。
7.2 模拟考试的正确姿势
模拟考试不是简单地找个题单做一遍,而是完整复刻考场流程。我的建议是:用计时器严格卡两小时,期间不清空思路、不查阅笔记、不跟人讨论,一气呵成。模考后要预留至少半小时做复盘,统计自己在哪道题上耗时最多、哪个环节最不熟练。
模考时还要刻意练习“读题—写注释—编码—自测”的完整流程。不要在脑子里直接编码,先花一两分钟把输入、输出、算法思路用注释写在代码上方,这个习惯能减少大量返工。
7.3 机试当天的时间分配策略
进考场后,我建议按这个节奏来:
- 0~5分钟:浏览全部题目,标注难度和预估耗时,决定做题顺序。
- 5~55分钟:快速做完第一题,务必拿满。如果中途卡住超过15分钟,先跳过,做后面的题。
- 55~95分钟:主攻第二题。完成后至少留5分钟,专门测试边界用例。
- 95~115分钟:尝试压轴题。写出暴力解或部分解法,哪怕只过一半用例也有分数。
- 115~120分钟:全面检查提交的代码,确认输出格式、无明显拼写错误、没有调试残留的打印语句。
这个时间线不是死的,但核心原则是:先易后难、留出检查时间。很多同学最后交上去的代码里还留着print调试语句,这种低级失误完全可以通过最后几分钟检查避免。
最后再分享一点个人体会:机试本质上是一场“在限定条件下快速输出正确代码”的考试,它考的不是你懂多少算法,而是你在压力下能不能把已知的东西稳定发挥出来。所以备考的重心不该放在“我要刷五百道题”这种数量目标上,而应该放在“我能不能把每类高频题的三板斧练到肌肉记忆”。我在实际准备中收益最大的,不是某道难题的解法,而是那一套输入输出模板和几个核心算法的代码骨架,它们帮我省下了大量考场上的低级错误时间。