资讯动态

OD机试算法题备考全攻略:高频题型与代码实战解析

发布时间:2026/10/16 23:51:47 来源:尧图企业网站定制
正在折腾某大厂OD机试的朋友不管你是刚转行刷算法不久还是已经把题单刷过一遍但一上机就发虚的人这篇内容应该都对得上胃口。OD机试说白了就是一场限时算法题考试支持Python、JS、C、C等主流语言一到两小时完成若干道编程题核心考点基本固定在字符串、数组、贪心、搜索、动态规划这些高频知识上。之所以单独把它拎出来写是因为我在陪练过程中发现大部分人挂在机试上并不是不会写代码而是不熟悉判题系统的输入输出写法、边界场景和性能约束。所以今天我按“规则理解→题型套路→真题实测→语言细节→避坑排查”的顺序把个人刷题和辅导积累的经验整理成一篇可以照着执行的文章。1. 机试规则与备考逻辑先弄清楚“考官想考你什么”1.1 OD机试到底考什么——题量、时长与评分点OD岗的机试并不会像校园笔试那样出大量选择题它基本就是“在线编程题”的天下。我自己接触过的几套题目和带人练习时的反馈来看常见配置是3到4道题总时长在90到150分钟之间。题目难度有梯度前一两题通常是字符串处理、数组统计这类“手热题”中间会夹一道贪心或搜索题最后压轴的往往是动态规划或复杂模拟。每道题的分值随难度递增所以策略上最忌讳一上来就死磕难题把前两题的分数拿到手基本就稳了一半。评分端到端由机器判题少数情况有人工抽检。机器打分主要看用例通过数也就是你跑的测试用例能过几个算几个。这意味着你的代码即使能算出样例只要某个隐藏边界没处理照样丢分。比如数组为空、n等于0、时间戳为负数、输入行末尾有空格这些都需要cover。另外代码风格不会单独给分但写得清晰对你调试和检查有实质帮助别把变量名起成a1、b2、tmp3满天飞。还有一个容易被忽略的细节题目给的答题语言自由度比较高Python、JS、C、C都可以选。你不需要“样样精通”但至少要保证你选的语言能在判题环境里正确读取标准输入和输出。很多人算法思路全对结果因为input()读取超时、stdin缓存问题或没写readline监听而白丢分。这类问题在一开始就要彻底解决。1.2 目标导向备考先把高频考点做成清单与其漫无目的地刷LeetCode不如把目标企业的机试考点按出现频率排个优先级。根据我刷题和复盘的经验可以整理成一张“必会清单”考点类别典型问法推荐掌握优先级字符串子串判断、字符计数、压缩展开、去重排序必会数组前缀和、差分、双指针、滑动窗口必会排序自定义排序、区间合并、TopK必会贪心活动安排、任务调度、加油站问题高频搜索网格DFS/BFS、最短路、岛屿类问题高频动态规划背包、最长子序列、编辑距离、路径计数高频模拟日期处理、进制转换、状态变更中频备考时不建议一上来就奔着难题去先把清单里的基础题型刷透。每个类别至少独立实现三题再换语言重写一遍比如用Python写完再用C写一遍效果远好于今天刷字符串、明天跳去刷图论。目标很明确把高频题型的“解题手感”变成肌肉记忆考试里看到相似问法就能直接套模板。1.3 判题系统的隐性规则很多第一次参加机试的人会忽略一个问题判题系统怎么读你代码的输入多数平台是通过标准输入传输测试数据的。Python要用input()或sys.stdin读取JS在Node环境里要监听readline事件C直接用cinC语言则是scanf。最稳妥的做法是提前准备一个“标准输入模板”把整段数据一次性读进来再按空白符切分这样能避免一行一行解析时被空行、多余空格坑到。输出也有一点讲究一般只输出结果本身不要加“结果是”这类提示语否则会被判为错误。如果题目要求输出多行注意换行是否缺了最后一行的\n。我自己见过一个很典型的失败案例代码逻辑完全正确唯一的问题是在打印答案时多输出了一个空行结果整题判错。这些隐性规则不提前摸清考场上只会白白浪费心理预算。2. 高频题型分析与核心解法吃透这几类题就赢了大半2.1 字符串与数组手热题也是基本盘第一题经常是字符串或数组题目的是快速筛选“能不能写代码”。这类题不考高深算法核心是基本功你会不会按分隔符拆字符串会不会统计频率会不会处理越界。比如“统计一个字符串里出现次数最多的单词并按字典序输出”本质就是split加哈希表再加一次排序。很多人觉得简单却会因为没考虑单词大小写、标点符号或者连续多个空格而出错。数组题里前缀和和滑动窗口是两种极高频率的解。前缀和适合“连续子数组的和等于多少”这类问题开一个presum数组让presum[i]表示前i个元素的和那么[l, r]区间和直接用presum[r1] - presum[l]算出来复杂度从O(n^2)降成O(n)。滑动窗口适合“寻找满足条件的最大最小窗口”核心是维护左右两个指针右指针扩张左指针收缩窗口内用哈希表或计数数组维护状态。做这类题时要特别小心索引。我建议在草稿纸上先画几组小数据比如长度为0、长度为1、长度为5的数组把循环边界和指针移动逻辑跑一遍再上机。很多数组题挂掉不是思路错而是for循环里1和-1打反了。2.2 贪心与区间类二十分钟拿下中等题的秘诀贪心题是OD机试里的“性价比之王”。它通常不需要非常复杂的代码结构难点在于想明白“每一步选择局部最优最终能否得到全局最优”。最经典的是区间调度问题有一堆活动每个活动有开始时间和结束时间问你最多能参加多少场。这个问题的标准解法是按结束时间排序然后从头到尾遍历每次选一个“能接上且结束时间最早”的活动。为什么按结束时间排序因为结束得越早就给后面的活动留下越多空间。这个直觉我在面试辅导中反复强调很多人第一反应是按开始时间排序或按持续时间排序结果都能找到反例只有按结束时间排序是对的。区间合并也常出现给你若干区间把有重叠的合并成一个。思路是先按左端点排序再维护当前区间的右边界如果下一个区间的左端点小于等于当前右边界就合并否则开启新区间。这类题配合自定义排序函数就能写得很干净记住C里用sort加lambdaJS里用sort((a,b) a[0]-b[0])Python里用sorted(keylambda x: x[0])。2.3 DFS/BFS与图论搜索题的代码模板搜索题属于机试中的“标准必考款”。最典型的就是二维网格题从某个格子出发能到达多少个格子或者求最短路或者统计连通块数量。DFS适合“全路径探索”BFS适合“最短步数”和“分层扩散”。我强烈建议把BFS模板练到闭眼能写。核心就四步初始化队列、初始节点入队、循环弹队首、向四个方向扩展并把合法未访问节点入队。唯一要注意的是距离数组或访问数组的设置避免重复入队。很多人BFS写超时就是因为没有记录已访问节点同一个格子被压进队列几十次。DFS同样有固定套路设计递归函数传入当前坐标先判断边界和障碍再访问当前格然后向四个方向递归。记得回退时要正确处理“访问标记”否则要么无限递归要么漏掉路径。对于“岛屿数量”这类题DFS比BFS代码更短所以我一般建议连通块题用DFS最短路径题用BFS不要混着纠结。2.4 动态规划背包、序列、路径问题动态规划在OD机试里通常是压轴题也是区分“入门”和“通关”的分水岭。但只要掌握几类固定模型中等难度的DP题其实并不玄。一个是背包问题。无论是0-1背包、完全背包还是多重背包核心都是“定义状态状态转移”。0-1背包的经典状态是dp[j]表示容量为j时能获得的最大价值遍历每个物品时从后往前更新容量因为每个物品只能用一次。完全背包则改为从前向后更新允许同一个物品叠加使用。这个“从后往前”和“从前往后”的区别是无数人栽跟头的地方。另一个高频模型是“最长上升子序列”和“编辑距离”。它们的共性是定义dp[i]表示前i个元素的最优值然后考虑第i个元素选或不选找出转移方程。建议把几个经典题目分类整理比如“路径计数”、“最小路径和”、“最长公共子序列”各记一道模板题考试时先判断它属于哪个模型再套模板微调即可。最后哪怕遇到完全不会的DP题也不要空着。可以先写出暴力递归再尝试加备忘录做记忆化搜索这样至少能通过部分小数据用例比交白卷强得多。3. 真题样例与三语言实战从“读题”到“AC”的全过程3.1 样例题最多能参加多少场会议贪心题目描述输入第一行为整数n表示有n场会议。接下来n行每行两个整数start和end表示会议的开始和结束时间。你最多能参加多少场会议一个人同时只能参加一场且一场会议结束后可以立刻开始下一场。样例输入5 1 3 3 5 2 6 4 7 5 9样例输出3思路非常明确按结束时间排序然后贪心选择。维护一个变量lastEnd记录上一场已选会议的结束时间遍历排序后的会议只要当前会议开始时间不小于lastEnd就选中它并更新lastEnd。Python实现import sys def solve(): data list(map(int, sys.stdin.read().split())) if not data: return n data[0] meetings [] idx 1 for _ in range(n): s, e data[idx], data[idx 1] meetings.append((s, e)) idx 2 meetings.sort(keylambda x: x[1]) count 0 last_end -10**9 for s, e in meetings: if s last_end: count 1 last_end e print(count) if __name__ __main__: solve()JS实现const readline require(readline); const rl readline.createInterface({ input: process.stdin, output: process.stdout }); const lines []; rl.on(line, line lines.push(line.trim())); rl.on(close, () { const n parseInt(lines[0], 10); const meetings []; for (let i 1; i n; i) { const [s, e] lines[i].split(/\s/).map(Number); meetings.push([s, e]); } meetings.sort((a, b) a[1] - b[1]); let count 0; let lastEnd -Infinity; for (const [s, e] of meetings) { if (s lastEnd) { count; lastEnd e; } } console.log(count); });C实现#include bits/stdc.h using namespace std; int main() { int n; cin n; vectorpairint, int meetings(n); for (int i 0; i n; i) { cin meetings[i].first meetings[i].second; } sort(meetings.begin(), meetings.end(), [](const auto a, const auto b) { return a.second b.second; }); int count 0; int lastEnd -1e9; for (const auto [s, e] : meetings) { if (s lastEnd) { count; lastEnd e; } } cout count endl; return 0; }这段代码的时间复杂度是O(n log n)瓶颈全在排序。如果你用的是C语言可以用结构体数组加qsort注意比较函数写法要有防御避免比较结果溢出。我特别提醒一点lastEnd的初始值要足够小一般取负无穷不要默认取0。因为会议开始时间允许为0如果初始值是0第一场开始时间为0的会议就会被误判为“已经冲突”白白丢分。3.2 样例题网格里距离出口最远的空格多源BFS题目描述给出一个m行n列的网格网格由0和1组成。1代表出口0代表普通格子。一个人每次可以向上下左右移动一格每格耗时1。请问从任意一个0格子出发到达最近出口的最大可能距离是多少样例输入3 3 0 0 0 1 0 1 0 0 0样例输出1这道题典型的做法是多源BFS把所有出口先当成队列里的起点距离初始化为0然后一起向外扩散。这样每个格子第一次被访问时记录的便是到最近出口的距离最后遍历所有0格子取最大值即可。Python实现import sys from collections import deque def solve(): data sys.stdin.read().strip().split() if not data: return idx 0 m int(data[idx]); idx 1 n int(data[idx]); idx 1 grid [] for _ in range(m): row list(map(int, data[idx:idx n])) idx n grid.append(row) dist [[-1] * n for _ in range(m)] q deque() for i in range(m): for j in range(n): if grid[i][j] 1: dist[i][j] 0 q.append((i, j)) 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 dist[nx][ny] -1: dist[nx][ny] dist[x][y] 1 q.append((nx, ny)) ans 0 for i in range(m): for j in range(n): if grid[i][j] 0: ans max(ans, dist[i][j]) print(ans) if __name__ __main__: solve()JS实现const readline require(readline); const rl readline.createInterface({ input: process.stdin, output: process.stdout }); const lines []; rl.on(line, line lines.push(line.trim())); rl.on(close, () { if (!lines.length) return; const first lines[0].split(/\s/).map(Number); const m first[0]; const n first[1]; const grid []; for (let i 0; i m; i) { grid.push(lines[i 1].split(/\s/).map(Number)); } const dist Array.from({ length: m }, () Array(n).fill(-1)); const queue []; for (let i 0; i m; i) { for (let j 0; j n; j) { if (grid[i][j] 1) { dist[i][j] 0; queue.push([i, j]); } } } const dirs [[-1, 0], [1, 0], [0, -1], [0, 1]]; let head 0; while (head queue.length) { const [x, y] queue[head]; for (const [dx, dy] of dirs) { const nx x dx; const ny y dy; if (nx 0 nx m ny 0 ny n dist[nx][ny] -1) { dist[nx][ny] dist[x][y] 1; queue.push([nx, ny]); } } } let ans 0; for (let i 0; i m; i) { for (let j 0; j n; j) { if (grid[i][j] 0) { ans Math.max(ans, dist[i][j]); } } } console.log(ans); });C实现#include bits/stdc.h using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int m, n; cin m n; vectorvectorint grid(m, vectorint(n)); vectorvectorint dist(m, vectorint(n, -1)); queuepairint, int q; for (int i 0; i m; i) { for (int j 0; j n; j) { cin grid[i][j]; if (grid[i][j] 1) { dist[i][j] 0; q.push({i, j}); } } } int dx[4] {-1, 1, 0, 0}; int dy[4] {0, 0, -1, 1}; while (!q.empty()) { auto [x, y] q.front(); q.pop(); for (int k 0; k 4; k) { int nx x dx[k]; int ny y dy[k]; if (nx 0 nx m ny 0 ny n dist[nx][ny] -1) { dist[nx][ny] dist[x][y] 1; q.push({nx, ny}); } } } int ans 0; for (int i 0; i m; i) { for (int j 0; j n; j) { if (grid[i][j] 0) { ans max(ans, dist[i][j]); } } } cout ans \n; return 0; }这类搜索题有个常见坑如果网格里没有出口理论上所有距离都是-1题目数据通常不会这样出但你可以在代码里补一个判断避免输出一个奇怪的大数。多源BFS的时间复杂度是O(mn)因为每个格子最多入队一次。3.3 样例题预算有限怎么投资价值最大0-1背包题目描述你有W元预算有n个项目每个项目需要投入一定金额并能获得一定收益。每个项目最多只能做一次请问在预算内能获得的最大收益是多少样例输入4 10 2 5 3 6 4 7 5 9样例输出16这就是标准0-1背包。dp[j]表示花费恰好不超过j元时的最大收益遍历每个项目从预算上限往下更新。Python实现import sys def solve(): data sys.stdin.read().strip().split() if not data: return idx 0 n int(data[idx]); idx 1 w int(data[idx]); idx 1 costs [] values [] for _ in range(n): costs.append(int(data[idx])); idx 1 values.append(int(data[idx])); idx 1 dp [0] * (w 1) for i in range(n): for j in range(w, costs[i] - 1, -1): dp[j] max(dp[j], dp[j - costs[i]] values[i]); print(dp[w]) if __name__ __main__: solve()JS实现const readline require(readline); const rl readline.createInterface({ input: process.stdin, output: process.stdout }); const lines []; rl.on(line, line lines.push(line.trim())); rl.on(close, () { const nums lines.join( ).split(/\s/).map(Number); const n nums[0]; const w nums[1]; const costs []; const values []; let idx 2; for (let i 0; i n; i) { costs.push(nums[idx]); values.push(nums[idx 1]); idx 2; } const dp Array(w 1).fill(0); for (let i 0; i n; i) { for (let j w; j costs[i]; j--) { dp[j] Math.max(dp[j], dp[j - costs[i]] values[i]); } } console.log(dp[w]); });C实现#include bits/stdc.h using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, w; cin n w; vectorint dp(w 1, 0); for (int i 0; i n; i) { int cost, value; cin cost value; for (int j w; j cost; --j) { dp[j] max(dp[j], dp[j - cost] value); } } cout dp[w] \n; return 0; }这里有一个十个人里八个会犯的错0-1背包内层循环一定要从后往前。如果从前往后更新同一个项目会被重复使用那就变成完全背包了。如果你需要做完全背包才把内层循环改成从cost[i]遍历到w。这一字之差结果完全不同。建议每次写完都手动跑一遍样例确认是0-1还是完全。4. 不同语言的答题建议与工程细节4.1 Python语法最爽但要注意输入输出与递归深度如果你平时写Python最多那机试用它最舒服。代码量短调试直观处理字符串和数组特别方便。不过有几点要提前适应。第一读入速度。数据量大的时候用sys.stdin.read()一次性读取再切分比反复调用input()快很多。每调用一次input()都有系统开销几千行数据无所谓如果测试数据到十万行差距就会变得明显。我一般用data sys.stdin.read().split()把整块内容变成字符串列表再按需转成int。第二递归深度。Python默认递归深度上限大约1000层如果DFS需要深入网格几百上千层可能会直接RecursionError。建议在文件顶部加上sys.setrecursionlimit(1000000)至少防御一下。不过纯递归仍然有栈溢出风险递归特别深时还是改写成显式栈的DFS或直接用BFS。第三别花太多时间优化常数。机试更看重复杂度是否达标Python跑不过C的常数项这是语言特性不需要强求。只要你的复杂度是正确的通常能拿满用例分。4.2 JavaScript数组方法库丰富但别被输入输出坑到前端转算法岗的朋友常选JS因为对数组方法非常熟。map、filter、reduce处理数据确实方便但机试环境和浏览器环境不同没有DOM也没有window只有Node.js的基本运行环境。JS的输入输出要用readline模块。很多人在本地调试时用的测试文件是直接console.log但一上机就傻眼不知道该从哪儿读入数据。标准姿势是监听process.stdin上的line事件收集所有行等close事件触发后再统一处理。这个模板最好写成自己的固定代码块考前直接复制别到了考场现场回忆。另外JS里的Number和parseInt有微小区别。parseInt(12abc)会返回12容易掩盖脏数据问题。机试题的输入通常干净但如果要做严格处理我习惯用Number或直接map(Number)让脏数据暴露出来而不是悄悄通过。4.3 C/C性能最稳但不要忽略编译细节C几乎是大厂机试的“标准答案语言”因为STL实在太强。vector、sort、queue、unordered_map直接拿过来用比手写轮子省出大量时间。用的时候注意几点一是输入输出提速。在main开头加上ios::sync_with_stdio(false); cin.tie(nullptr);能明显加快cin的速度。机试数据量大时这两行能避免因为输入慢导致超时。二是数据范围。见到10^9级别的数值优先使用long long而不是int。整数溢出的错误很难肉眼排查因为样例往往刚好能过隐藏用例一上大数就崩。我习惯看到“价格”“金额”“距离”这类数值统一用long long反正现在内存没那么紧张。三是如果你用的是纯C没有STL只能自己写数组和排序比较函数。qsort比较函数的写法容易踩坑返回值是int计算差值时可能溢出。安全写法是把两个数转成long long再比较或者用大于小于分别返回1和-1。能用C的场合尽量选C除非题目明确要求C。4.4 本地验证把“AC模板”固定下来不管你最终选哪种语言都建议在本地建立一个固定的模板文件里面包含完整的读取输入、解析数据和输出结果的代码骨架。每次做题先把模板复制出来再往里面填算法主体。这样至少能避免三类低级失误没有读取全部输入、输出格式不对、忘记处理空输入。模板之外还要准备一个“对拍”习惯。如果你有标准答案或者暴力解法可以生成随机小数据把暴力结果和你的优化算法结果对比。机器测试你的时候也是拿一堆用例跑你自己先模拟这个过程几乎能筛掉九成隐藏bug。5. 常见陷阱、调试技巧与经验手记5.1 输入输出陷阱别让“快”变成“挂”输入输出是最容易翻车但也最好修复的环节。常见问题有读入时没处理首行空行导致第一个数读错输出多打了空格或换行题目要求“每个结果占一行”你却在末尾多打印一个空行或者所有输出连成了一行。一个稳妥的输出策略是把结果先收集到列表里最后统一用join输出多行内容。Python里是\n.join(results)C里可以直接cout ans \n。不要把多个输出拆得七零八落回头检查时自己都容易看晕。还要特别留意多组测试数据的题目。有些题一口气给好几组输入要求每组输出一行答案代码必须放在一个循环里处理而不是只跑第一组就退出。遇到这种情况最好用一个while循环持续读入直到输入结束再逐组计算输出。5.2 本地通过但线上报错怎么排查这是所有机试考生最痛苦的情况本地样例全对交上去却是WAWrong Answer或RERuntime Error。我的排查顺序是这样的。首先查输入解析是不是把题目里的多行数据读错位了。其次查边界条件重点看n为0、n为1、数组长度为1、网格只有一行或一列的情况。再次查类型溢出尤其是C里int遇到乘法或累加。最后查算法逻辑有没有误把降序当成升序有没有忘了标记已访问节点。如果线上报的是超时不要急着改常数优化先检查自己的复杂度是否真的达标。一个典型的反面教材是本来用前缀和能O(1)求区间和结果写成了O(n)的sum切片最后在小数据上能过大数据直接超时。看到TLE先把每个循环的复杂度算清楚再决定要不要上更优解法。5.3 刷题节奏与心态管理备考阶段最容易犯的毛病是“刷得多复盘少”。我见过有人两周刷了三百道题但还是考不过因为每一道题都是看了答案就划走根本没有形成自己的思路。相比之下刷八十道题但每一道都自己推到AC并且对错误点做了记录效果会好得多。建议每天至少留半小时做错题复盘。不用重写整题只把“这题用到什么模型、我当时卡在哪里、下次看到什么特征该想到哪个套路”记下来。考试前一天不要再挑战新题更不要临时去学没见过的算法。把模板、错题、笔记全部翻一遍早点休息比熬夜刷题更有用。很多人对机试有心理压力总觉得一道题AC不了就完蛋。实际上机试看的是总分和用例通过率不是“全对才给分”。哪怕一道题只能跑过部分用例也能拿到相应分数。我辅导过的一位A同学最后一道DP题只写出了暴力版本但因为过了前两题加最后一题的一半用例总分依然冲过了线。所以考场上永远不要放弃能写多少写多少暴力解法也有它的价值。6. 从入门到通关的备考计划三阶段落地清单6.1 第一阶段打地基按章节过一遍时间建议一到两周。目标是掌握必会清单里的基础题型不需要追求难题偏题。每天固定两到三小时一小时看知识点和模板两小时动手写题。第一天先准备语言模板和环境把输入输出跑通。接下来按“字符串→数组→排序→双指针→栈队列”的顺序过基础章节。这个阶段不求速度求的是“不看答案自己能写出完整AC代码”。每一道题都要自己先想十分钟想不出来再看解析看完解析后合上答案重新写一遍这是最有效的学习闭环。6.2 第二阶段专题突破按题型刷中等题时间建议两到三周。开始针对贪心、动态规划、搜索做专题练习。贪心题多练“排序贪心”的组合搜索题把BFS、DFS模板固定下来动态规划题按背包、子序列、路径问题分组刷。这个阶段一定要做真题或接近真题难度的题不要只做LeetCode简单题。因为你最终面对的是限时环境下的题目平时就要模拟“读题、分类、套模板、写代码、自测”完整流程。每做完一题记录用时。如果一道中等题超过四十分钟还没AC先停下看解析复盘卡点是在读题、建模还是实现。6.3 第三阶段全真模拟按考试规格打排位进入考前一周就不要再零散刷题了改成全真模拟。严格给自己计时按照实际机试的题量和时长安排一次性做三到四道题。模拟时不要中途查手机、不要看解析、不要一题卡住就放弃整场。只有模拟够多你才知道自己在第40分钟时是什么状态在第90分钟时会不会手忙脚乱。模拟之后立刻复盘哪些题读题花了太多时间哪些代码写了又删哪些边界是考后才发现的。把这些问题写进错题本最后一天只重复这些内容。个人经验是到这一步你的机试通过率已经远超大部分没经过系统训练的候选人了。最后再分享一个小技巧正式机试时拿到题先把所有题快速扫一遍给它们标上“会做”“可能做”“完全没思路”三类。先花时间处理“会做”的题把分数稳稳拿到再回头啃“可能做”的题最后剩多少时间都给“没思路”的题写暴力代码。这个顺序看起来简单却是很多人考场上最容易忽略的胜负手。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑