
又到了每年的模考季翻出自己2019年参加牛客第五次模考编程题集合的答题记录感慨还挺多的。当时那套题我做得很狼狈前两道顺利通过第三道开始卡壳最后一道干脆没时间写。现在五年过去作为已经带过不少校招新人的工程师回头看这套题的价值其实远不止于刷一遍对答案——它几乎是笔试环境里所有隐藏考点的集中展示。我经常跟准备面试的朋友说牛客的模考题特别值得反复研究不是因为题目有多难而是因为它模拟的就是真实笔试环境限时、限空间、无法调试太久、一道题卡住就会影响后面。2019年这套五模的题型编排尤其典型既覆盖了字符串处理、模拟题、动态规划这些笔试常客又在数据规模和边界条件上埋了不少坑。今天这篇就把我当时复盘这套题的心得整理出来主要聊聊题型结构、解题套路、踩坑记录和错题复盘的方法希望给正在刷题准备笔试的朋友一些实际参考。1. 五年后再复盘这套题的出题逻辑和求职现实1.1 为什么五模值得单独拿出来说牛客的模考系列是分轮次推出的第一套到第五套各有侧重。前面几套通常偏基础考察的是你会不会写代码到了第五套考察重心已经变成你在限时和压力下能不能稳定写出正确的代码。这也是为什么很多人第一次做五模的时候分数很难看——题目本身的难度曲线在第五套有一个明显爬升而且混合了多个知识点的综合题明显增多。当时我所在的求职群里大家在同样时间做完这套题后的分数分布非常有意思能拿满分的没几个但集中在及格线上下的人特别多。这个现象本身就说明了问题——出题人在设计这套题时刻意把区分度做在了中档题上。基础题大家都会压轴题没几个人做得出来真正决定排名和面试机会的恰恰是中间那几道看起来不难但总有一两个细节会出错的题目。1.2 这套题折射的笔试筛选逻辑站在出题人的角度想笔试环节的核心目的不是考倒所有人而是用最短的时间筛掉代码功底不扎实和工程习惯不好的候选人。2019年五模这套题里输入输出格式的坑、边界条件的隐晦设置、时间复杂度的隐性要求其实都在模拟真实业务的排查场景。你写出的代码不仅要能跑还要在边界情况下不崩在数据量大的时候不超时。我当时把四道题全部复盘完最大的感受是会做题 ≠ 会考试。这套题里大约有三道题在思路上我都能很快想到正确解法但在考场上因为一个边界条件没处理好、因为一次输入读取方式不对硬生生浪费了二三十分钟。这些东西如果不通过模考暴露出来到真正的笔试现场就是灾难。所以别把模考当成普通的刷题练习它更重要的是帮你暴露问题。2. 从题型分布反推备考重心哪些分必须拿哪些分可以放2.1 高频考点与失分权重复盘2019年这套五模整体的考点分布大概是这样的结合牛客历年模考的常见题型结构这类题的出题范围都比较稳定题型类别大致占比常见考点失分原因字符串处理25%左右反转、去重、子串匹配、格式化边界不清、分隔符处理错误模拟题25%左右按规则一步步执行、日历/游戏规则状态不完整、循环条件错误排序与查找20%左右自定义排序、二分查找变体比较器逻辑写反、索引偏移数据结构应用15%左右栈、队列、链表、哈希表数据结构选型不当、复杂度超标动态规划15%左右背包、最长子序列、状态机DP状态定义不清、转移方程漏情况这个分布其实和大多数互联网公司笔试的出题比例相当接近。字符串处理和模拟题加起来占了将近一半这意味着你不需要成为算法竞赛选手只要把这两类基础题的套路练熟分数线就已经稳了一半。反过来如果你一上来就死磕动态规划和图论基础题反而翻车那才是真正的得不偿失。2.2 基础题才是分水岭很多人在备考的时候有一个误区总盯着最难的那道题复习觉得只要我能写出压轴题前面的题目都不在话下。但真实情况是笔试里最可惜的丢分不是压轴题没做出来而是前面的基础题因为各种莫名其妙的原因出错——超时、越界、读入错误、输出格式不对。我在复盘这套题的时候把四道题按必须拿分和尽力拿分做了分类。字符串和模拟相关的题目属于前者这些题只要思路清晰代码量不会太大是笔试的基本盘。后面涉及动态规划或复杂数据结构的题目属于后者能做出来是加分项做不出来也不该影响心态。把这个逻辑想清楚你在考场上的时间分配就会理性很多不会因为一道难题卡住而放弃整个大局。2.3 难题的取舍策略再具体一点说取舍。第五套模考的最后一道题我当时看了十分钟没有完整思路果断先跳过去把前面几道题的边界测试全部补了一遍回头再来看压轴题虽然最后只拿到了部分分数但前面三道题都稳住了。这个策略在真实的笔试里非常实用大部分平台的判题规则是按通过用例比例给分部分通过也是分比空着交卷强太多了。3. 字符串与模拟题的通解套路把程序题变成填空题3.1 输入输出基本功Python最容易被扣分的隐形题先说一个最基础但最容易被扣分的环节——输入输出。2019年那会儿很多人还在用C写笔试但最近几年用Python的人越来越多了我自己也主要用Python。Python在写算法题时确实快但输入输出处理上有不少坑。笔试平台通常支持两种读取方式input()和sys.stdin.readline()。很多初学者习惯用前者但模考数据量一大input()的性能问题就暴露出来了。举个例子import sys def solve(): data sys.stdin.read().strip().split() # 处理所有输入一次性把全部输入读进来再分割通常比一行行readline()更快代码也更简洁。但这要求你先把数据结构想清楚第一行是什么、后面几行是什么、什么时候结束。我在模考时踩过的坑是有的题目输入数据之间用多个空格分隔有的用逗号有的行末还有多余空格直接用split()有的能处理有的不能必须根据题意灵活选择。一个小技巧在读入阶段就顺手把数据转换成目标类型不要等到用的时候再转。这样既能提前暴露格式问题也能减少主逻辑里的类型转换噪音。3.2 状态机思路处理文本类题目说完输入输出再聊一个我在复盘字符串题时觉得特别有用的套路状态机。2019年这套模考里有好几道题本质上都是按规则逐字符处理比如简化路径、解析字符串、判断括号合法性之类。这种题如果你在循环里堆一堆if-elif写着写着就会漏掉状态之间的转换关系。更好的方式是先把状态画出来想清楚每个状态下遇到什么字符应该跳到哪个状态然后再写代码。举一个典型的例子判断一个字符串是否是合法的数字包含整数、小数、科学计数法def is_number(s: str) - bool: s s.strip() if not s: return False i 0 n len(s) # 跳过正负号 if s[i] in -: i 1 is_num False is_point False is_exp False while i n: c s[i] if c.isdigit(): is_num True i 1 elif c . and not is_point and not is_exp: is_point True i 1 elif c in eE and is_num and not is_exp: is_exp True is_num False # 指数部分必须重新出现数字 i 1 elif c in - and i 0 and s[i-1] in eE: i 1 else: return False return is_num这种代码你直接硬写很容易漏掉e后面必须跟数字、小数点只能出现一次这些细节。但如果你先在草稿纸上把状态列出来——起始态、符号态、整数态、小数态、指数态——再对应写代码每个分支都有依据正确率会高很多。3.3 典型模拟题的答题节奏模拟题的解题节奏我总结为三步读清楚规则、确定数据结构、逐步实现并打印中间结果验证。以一道常见的模拟题为例要求模拟一个排队叫号系统给定操作序列包含顾客到达、窗口叫号、顾客离开输出最终每个窗口服务的顾客数。这种题不涉及高深算法但你需要把每个操作对应的数据结构想清楚。队列存等待顾客数组存窗口状态每个操作是一个事件按时间顺序处理就行。写完后一定要用题目给的示例数据手动跑一遍再提交。很多人在模拟题上丢分不是因为不会而是因为没有按示例逐步验证。我自己习惯在写完模拟题后把关键变量的变化过程打印出来和题目示例的中间过程比对一次就能定位逻辑错在哪。4. 动态规划和数据结构题识别变体的三条经验4.1 DP题的状态定义是灵魂动态规划在模考中通常只占一道题但这一道题往往就是区分度所在。我复盘2019年这套五模里的DP题当时考的是类似最长有效括号的变体时最大的体会是状态定义对了转移方程就是顺水推舟状态定义错了怎么调都调不对。动态规划题的核心步骤永远只有两步第一步是定义dp[i]表示什么第二步是找出dp[i]和前面状态的关系。以最长有效括号为例def longest_valid_parentheses(s: str) - int: n len(s) dp [0] * n res 0 for i in range(1, n): if s[i] ): if s[i-1] (: dp[i] (dp[i-2] if i 2 else 0) 2 elif i - dp[i-1] - 1 0 and s[i - dp[i-1] - 1] (: dp[i] dp[i-1] 2 (dp[i - dp[i-1] - 2] if i - dp[i-1] - 2 0 else 0) res max(res, dp[i]) return res这个题我第一次做的时候状态定义成以i结尾的最长有效括号长度这个定义本身没问题但转移方程里dp[i-1]的匹配情况容易漏。后来我总结了一个经验DP题写完转移方程之后一定要找一组比较长的测试用例手动按索引推一遍确认每个分支都覆盖到了再提交。4.2 数据结构选择的快速判断模考里的数据结构题通常不会要求你手写红黑树这种高级结构考得最多的还是栈、队列、链表和哈希表。关键是要快速判断出该用哪个结构。我记得那套题里有一道链表相关的题目类似链表中的下一个更大节点很多人在考场上第一反应是双重循环O(n^2) 的复杂度在数据量一大就超时。但实际上用单调栈可以做到 O(n)。这个经验很重要遇到找下一个更大/更小元素这类问题优先想到单调栈遇到最近配对问题优先想到栈遇到滑动窗口最值优先想到双端队列。数据结构选对了很多题就是模板题。这里可以给大家一个快速判断的小表格题目特征优先考虑的数据结构最近匹配、嵌套关系栈排队、公平调度队列高频访问、快速查找哈希表数据量小但频繁增删链表区间最值、滑动窗口双端队列 / 堆4.3 时间复杂度估算动手前先算一算2019年那套题里有一道排序变体题题目数据范围给到了 10^5意味着 O(n^2) 的解法肯定会超时。我当时第一反应是直接sort加自定义比较器这个选择是对的因为 Python 内置排序是 Timsort平均 O(n log n)完全能过。但如果你在考场上写了冒泡或者插入排序那大概率会卡在超时上。做题之前花十秒钟估算一下复杂度是笔试的基本习惯。10^5 的数据规模n log n 的算法大约是 10^6 次操作Python 可以轻松承受n^2 则是 10^10 次操作Python 绝对跑不动C 也够呛。所以读完题先看数据范围再选择解法比什么都重要。5. 模考实战踩坑记录五个高频翻车点与止损方案5.1 输入读取的隐形坑第一道题我当年就栽在输入读取上。题目给的是多组测试数据每组之间有空行我直接用input()一行行读结果遇到空行就报错。后来改成用sys.stdin.read()把全部内容一次性读进来再按规则分割问题才解决。Python 笔试里如果题目说输入包含多组测试数据以 EOF 结束不要用input()在循环里判断是否读到空字符串那样在牛客的判题环境里很容易出问题。正确做法是import sys for line in sys.stdin: line line.strip() if not line: continue nums list(map(int, line.split())) # 业务逻辑5.2 边界条件的差一错误差分、握手问题、数组循环这些场景里的边界是最容易出 bug 的。我复盘自己那次的失分点最典型的错误是循环里用了while i n但数组长度只有 n访问arr[i]时越界。Pyt hon 里列表越界不会像 C 那样直接崩但会抛IndexError而且这个错误往往是在最后一个用例才触发前面都正常排查起来特别费劲。我的经验是凡是涉及索引的循环先问自己三个问题——循环变量从 0 开始还是从 1 开始终止条件是还是数组最后一个元素的下标是多少这三个问题想清楚再写循环基本能规避掉一半以上的越界 bug。5.3 时间换空间的错误判断第五套模考里有一道题我第一反应是开一个超大数组做记录这样可以 O(1) 判断某个值是否出现过。但题目给定的值域特别大开到 10^9 的数组肯定不现实。这就是典型的空间换时间的错误用法。正确做法是用哈希表Python 的dict或set在数据量可控的情况下表现得很好。所以遇到需要快速查找的题目先评估值域范围。值域在 10^6 以内可以用数组值域更大老老实实用哈希表。这两者的性能差距在笔试数据规模的测试下往往不大但代码健壮性区别很大——数组开太大直接内存超限哈希表只是稍微慢一点。5.4 递归深度问题Python 的递归深度默认只有 1000 层这在遇到树形结构或者某些递归写法时会直接爆RecursionError。我在模考时遇到一道需要遍历二叉树变体的题目递归函数写到一半突然报错当时整个人都懵了。解决办法有两种一是提前设置递归深度上限import sys sys.setrecursionlimit(10**6)二是把递归改成显式的栈迭代。在笔试场景下我强烈建议优先尝试第二种。因为设置递归深度只是缓兵之计如果递归本身写得不高效照样会栈溢出或超时。很多树的题用迭代写法反而更直观# 二叉树前序遍历的迭代写法 def preorder(root): res [] stack [root] while stack: node stack.pop() if node: res.append(node.val) stack.append(node.right) stack.append(node.left) return res5.5 卡题止损三十分钟原则聊一个心态层面的坑。模考和真实笔试一样最怕的就是在一道题上死磕。我当时在第二道题上耗了太多时间导致后面整体节奏崩掉。后来我给自己定了一个规矩一道题超过 30 分钟没有完整 AC 思路立刻标记为待定赶紧做后面的题。等所有会的题都拿完分再回头啃硬骨头。这个止损策略在我后续的笔试里帮了大忙。要知道笔试的判题规则是按通过的用例给分你花 30 分钟把一道题从 40% 通过率提升到 60%远不如用 15 分钟把另一道题从 0 提升到 100%。时间管理的优先级永远是把容易的分先拿到手。6. 错题复盘模板一套题怎么榨出三倍价值6.1 错题分类的三个维度拿到模考成绩之后别急着看题解先自己给错题分类。我常用的分类维度有三个知识盲区这个考点我完全没见过或者知识体系里没有对应的解法。疏忽失误思路正确但边界、输入、类型转换等细节出错。复杂度问题思路正确但超时或超内存。把每道错题归入其中一个维度复习的思路就清晰了。知识盲区需要补充学习疏忽失误需要加强练习复杂度问题需要优化思维。这三个维度对应完全不同的提升策略混在一起复习效率很低。6.2 三轮复盘法一轮复盘当天合上题解把错题重新独立做一遍。如果还是不能 AC说明思路没有真正掌握而不是粗心。二轮复盘三天后不看任何笔记重新做这四道题。这次重点看能不能想起当时的思路并且用更简洁的方式实现。三轮复盘一周后只做变式——把原题的条件改一改。比如把最长有效括号改成删除最少的括号使字符串有效看自己在新的场景下能不能迁移思路。这三轮下来一套模考的价值基本上能顶三套题。很多人的问题是只做了一轮复盘当时看懂了题解就以为自己会了结果一周后再做还是不会就是这个原因。6.3 建立个人错题档案最后分享一下我的错题档案模板。我不太建议抄题那样太费时间我通常只记录几个关键字段题目来源与编号比如2019牛客五模 T3涉及考点2-3 个标签如动态规划/栈/字符串错误原因从三个维度选一个一句话解法总结比如单调栈维护索引递减遇到更大元素时弹出并记录答案再错次数用 Excel 或笔记软件维护都行关键是每次笔试前把这本档案翻一遍。你会发现真正反复错的永远是那几个点把它们攻克了笔试水平会有一个很明显的提升。我自己的体会是模考分数一点点上涨的过程其实就是错误档案从厚变薄的过程。做这套2019年第五次模考的时候我的档案里记录了很多现在看来很基础的错误但正是这些错误帮我在后来的笔试里避开了同样的坑。如果你现在正在准备校招或者跳槽笔试真心建议不要只刷题不对答案找一套完整的模考严格按考场时间做一遍再按上面这套方法复盘。把每一次模考都当成发现问题的机会比单纯追求刷题数量要有效得多。