2019年牛客第五次模拟考试的编程题集合,我到现在还有印象。那时候秋招刚进入白热化,身边同学人手一个牛客账号,每周模考雷打不动。五模这个场次比较特殊,它不像前几模那样偏重单一考点,而是把字符串处理、排序、动态规划这些高频题型混在一起考,难度梯度拉得很开,做起来非常有真实大厂笔试的感觉。
这篇文章不是要把每道题的题面复述一遍,而是想聊聊拿到这么一套编程题集合之后,完整的学习路径应该是什么:怎么审题、怎么选算法、怎么写输入输出、怎么分配时间、怎么复盘。不管你是在准备校招笔试,还是单纯想练算法手感,这套方法论都可以直接拿去用。我尽量把当年踩过的坑和后来总结出来的经验都写进去。
1. 牛客模考到底在模拟什么,五模为什么值得反复做
1.1 模考系统与真实笔试的关系:差的只是题目难度,不是流程
牛客模考本质上做了一件很朴素的事:把真实笔试的流程搬到浏览器里。限时开始、在线写代码、系统自动判题,所有操作都在一个类似OJ的页面上完成。很多人第一次参加在线笔试时会慌,不是因为题不会,而是不习惯那种页面上没有本地IDE辅助、只能在网页里敲代码的感觉。模考练的就是这个。
五模作为2019年度系列的第五场,题目设计上已经过了“照顾情绪”的阶段。前几模通常有比较多的送分题,五模开始出现一些需要绕弯子的题目,比如要用到贪心思路、需要自己推导状态转移方程,甚至有些题暴力能过样例但过不了全部测试点。这种设计风格和真实笔试非常接近:它有明显的难度分层,也逼着你在有限时间里做取舍。
所以我的建议很简单:把五模这类模考当成一次真正的笔试来对待,不要中途退出,不要查资料,不要暂停计时。分数不重要,感受整套流程的完整性和压力感才是重点。
1.2 从2019到现在的考点迁移:核心算法一直没变
有人可能觉得2019年的题太老了,没有参考价值。我反而觉得正好相反。校招笔试的考点更新迭代很慢,字符串、排序、二分、动态规划、栈队列这些核心内容,今天的主流笔试依然在考。变化的只是包装方式:可能题干换了个新场景,从“排列队伍”变成了“优化系统调度”,但底层算法模型还是那一套。
我整理了一下这类模考题集里最常见的考点分布,你可以对照着看自己哪里薄弱:
| 考点 | 出现频率 | 建议投入度 |
|---|---|---|
| 字符串处理(括号匹配、子串统计) | 很高 | 优先掌握 |
| 数组与排序(逆序对、第K大、next permutation) | 很高 | 优先掌握 |
| 栈与队列的模拟应用 | 高 | 必须熟练 |
| 动态规划(背包、路径、子序列) | 高 | 系统训练 |
| 贪心算法 | 中高 | 多练习证明 |
| 二叉树/图论 | 中等 | 视目标公司而定 |
| 数论/位运算 | 低 | 有余力再补 |
五模的题目集合基本覆盖了前五项。这也是我推荐大家重复刷的原因:它不是一个偏门套题,而是一个覆盖面广、层次分明的训练场。
2. 拿到一套编程题,先学会“拆题”比直接写代码更重要
2.1 三分钟读题法:先看四个信息点,而不是急着看样例
我见过太多人拿到题就开始敲代码,结果敲到一半发现理解错了题意。这个习惯在时间紧张的笔试里非常致命。后来我总结了一个“四看”读题法,看题阶段先找四个信息点:数据范围、输入格式、输出格式、样例解释。顺序不能乱。
数据范围决定算法选型,输入输出格式决定解析逻辑,样例解释帮你确认对题意的理解是否正确。举个例子,如果题目说 n ≤ 10^5,那么 O(n^2) 的暴力做法基本可以直接放弃;如果输入描述里写了“多组数据,以EOF结束”,你就得用循环读取而不是只读一次。
我建议在这上面花足三分钟。读题时间不是浪费,它可以避免你把二十分钟花在一道理解错误的题上。所谓“题目都会,就是没读懂”,在笔试里是最亏的丢分方式。
2.2 把题面翻译成算法模型的套路:题面关键词和算法是能对上的
读题不仅是理解字面意思,更关键的一步是把中文描述“翻译”成算法问题。这个过程熟练之后,做题速度会有质的提升。我给你列一个我自己常用的“翻译表”:
| 题干里的说法 | 对应算法方向 |
|---|---|
| “最大/最小” + “在某个区间内” | 二分答案 |
| “所有可能方案” / “统计组合数量” | 动态规划或组合数学 |
| “最早完成”“最晚结束” | 贪心排序 |
| “是否存在一条路径”“能否到达” | 图遍历、并查集 |
| “最近/最远” + 数组 | 双指针、单调栈 |
| “出现次数”“子串统计” | 哈希表 + 滑动窗口 |
这个表格不是绝对的,但它能帮你快速找到一个入口。哪怕一开始方向不完全对,也比对着屏幕干想强。能定位到一个可能的算法方向,后面的事情就是套模板、调边界、验证复杂度。
我刷五模这套题的时候,有一个很明显的感受:凡是能快速写出解的题,都是我能在第一分钟内判断出题型的题。凡是想半天没思路的题,基本都是卡在“翻译”这一步。
3. 高频考点的代码实现细节:这几类题最值得吃透
3.1 输入输出解析:一切代码的地基,写不对全白搭
牛客这类OJ平台的输入输出和平时在本地IDE里自己玩不太一样。在线笔试通常要求你从标准输入读数据,把结果输出到标准输出,中间不要有额外提示。我见过很多同学本地跑得好的代码,一提交就“答案错误”,有时候不是算法错了,是输入解析就没搞对。
给你一个通用的输入读取模板,我写Python比较多,直接用Python来演示:
import sys def solve(): data = sys.stdin.read().strip().split() if not data: return # 根据题目要求解析 idx = 0 # 第一行是n,表示数组长度 n = int(data[idx]); idx += 1 arr = list(map(int, data[idx:idx+n])) # 核心逻辑 result = your_function(n, arr) # 输出结果 print(result) if __name__ == "__main__": solve()这里的核心是sys.stdin.read(),它会把所有输入一次性读进来,然后用split()按空白字符切分。好处是不用手动处理换行和多余空格。坏处是如果输入里有字符串类型且字符串本身含空格,就需要换一种方式解析。所以每次写输入解析之前,一定要看一眼输入格式里每行是什么类型。
3.2 数组与排序场景:复杂度选择是分水岭
数组相关的题目在模考里占了不小比重,而且经常是区分度的关键。以“求数组中第 K 大的数”这类题为例,最简单的思路是排序后直接取下标:
def kth_largest(nums, k): nums.sort() return nums[-k]这段代码没有任何问题,时间复杂度是 O(n log n)。如果 n 只有 1000,这么写完全没问题。但如果 n 到了 10^7,排序就会超时。这个时候你就需要快速选择算法,平均时间复杂度 O(n),或者用堆来维护前 K 个元素,时间复杂度 O(n log K)。
笔试里到底选哪种,实际上取决于数据范围。我一般会先看 n 和 K 的量级:K 远小于 n 时用堆更稳,想追求理论最优就用快速选择的变形。这是一个典型的“复杂度选择决定生死”的场景,也是我想强调的点:同样一道题,暴力能过和暴力过不了,差别就在于你平时有没有养成看数据范围定复杂度的习惯。
3.3 字符串处理:边界情况的重灾区
字符串题看起来简单,但往往是最容易在边界条件上翻车的题型。比如括号匹配这种经典题目,很多人在笔试里都能写出来,但能一次 AC 的人不多。原因就是边界情况太多了:空字符串、只有左括号、只有右括号、括号交叉嵌套……
我看一道典型题的完整实现,就拿“判断括号字符串合法,并计算最大嵌套深度”来举例:
def max_depth(s: str) -> int: stack = [] depth = 0 for ch in s: if ch == '(': stack.append(ch) if len(stack) > depth: depth = len(stack) elif ch == ')': if not stack: return 0 # 右括号无匹配,非法 stack.pop() else: # 根据题目要求决定是否处理其他字符 continue if stack: return 0 # 左括号没匹配完,非法 return depth关键点在两个return 0的位置:用栈来匹配括号,如果遇到右括号时栈已经空了,说明当前的右括号没有对应的左括号,字符串非法;如果全部遍历完栈里还有元素,说明左括号多了。这两个边界检查缺一个都会让题目用例挂掉。
字符串题的一个通用建议是:写完代码之后,先在脑子里跑五个用例——空串、最简合法串、最简非法串、全同字符串、超长字符串。跑完再去提交,能省下很多次罚时。
3.4 动态规划入门:状态定义写清楚,代码只是翻译
动态规划是多数人的心理阴影,但模考里的动态规划题往往并没有那么可怕。关键点只有一个:把状态定义和转移方程写明白,代码就是一比一的翻译。
我以“网格最小路径和”为例,题目是给定一个 m x n 的网格,每次只能向下或向右走,求从左上角到右下角的最小路径和。第一步不是写代码,而是定义状态:dp[i][j]表示从起点走到(i, j)的最小路径和。转移方程是:
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])边界条件是dp[0][0] = grid[0][0],然后第一行只能从左边走过来,第一列只能从上边走过来。状态转移确定了,代码就很好写:
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 j in range(1, n): dp[0][j] = dp[0][j-1] + grid[0][j] for i in range(1, m): dp[i][0] = dp[i-1][0] + grid[i][0] for i in range(1, m): for j in range(1, n): dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]) return dp[m-1][n-1]我见过太多人一上来就直接写双层循环,写到一半才想起来状态没定义好,然后开始反复改。正确的顺序应该是:先在草稿纸上把状态和转移方程写出来,再动手写代码。这个过程养成习惯后,动态规划题的通过率会明显提升。
4. 实战策略:模考的时间管理和判题细节不能忽视
4.1 全卷答题时间分配:先做拿得分的,再啃硬骨头
一套编程题集合通常有三到五道题,时间一般是 90 到 120 分钟。很多人习惯按顺序做,遇到难题死磕四十分钟,最后简单题反而没时间写完。这是我觉得最亏的做法。
我自己的时间分配策略是这样的:拿到题目之后先花五分钟把所有题都扫一遍,然后给每道题标个难度等级。第一轮先把所有简单题做掉,目标是拿到基础分;第二轮做中档题;最后剩下的时间才用来攻最难的题。不要指望每道题都完全写出并 AC,很多时候你能稳定做出三道题,就已经超过了大部分人。
| 时间段 | 任务 | 目标 |
|---|---|---|
| 前5分钟 | 快速浏览全卷 | 标记难度,确定做题顺序 |
| 第5-40分钟 | 完成所有简单题 | 稳拿基础分,减少心理压力 |
| 第40-80分钟 | 主攻中档题 | 争取额外得分 |
| 最后20分钟 | 尝试压轴题/检查已提交代码 | 能得一分是一分 |
这套节奏需要平时通过模考来练习。你会发现,一旦你在时间分配上有了规划,场上就不会被某一道题拖到心态崩溃。
4.2 在线判题与本地运行的三大差异:提前踩坑,考场不慌
第一个差异是 Python 版本问题。本地可能是 Python 3.11,判题环境可能还是 3.6 或 3.8,一些新语法特性(比如int | None这种类型写法)在线上直接语法报错。写代码的时候尽量使用兼容性高的写法,不要秀太新的语法。
第二个差异是递归深度。本地递归深度默认大约是 1000 层,OJ 环境也一样,如果题目数据量较大且使用递归写法,很容易因为“递归层数超限”导致运行时错误。遇到树、图这类题目,优先考虑用循环加栈来替代递归,或者给递归加上sys.setrecursionlimit()把上限调高,但也要小心更深层的栈溢出。
第三个差异是输出量大的时候print太慢。如果结果要输出几万行,用 Python 的print逐行打印会很慢,经常直接超时。正确做法是把要输出内容先收集到一个列表里,最后用"\n".join(output_list)一次性输出。这个优化在模考里经常救我一命。
4.3 提交报错后的排查顺序:先分类型,再动手改
看到“答案错误”(WA)就疯狂改代码,是最没有效率的做法。我的建议是先判断错误类型,再决定下一步:
如果是“编译错误”(CE),优先检查语法、缩进、是否有未定义的变量,尤其是在线网页编辑器容易漏掉缩进问题。如果是“运行错误”(RE),重点检查数组越界、除零、空指针/None 取值、递归栈溢出。如果是“超时”(TLE),说明算法复杂度不合格,需要优化思路,而不是微调代码。如果是“答案错误”(WA),先检查有没有多种情况输出格式不对,例如多余空格、换行缺失、大小写不一致,再用小规模用例手动验证。
我一般会先用题目给的样例测一遍,再自己构造两到三组边界用例。如果边界用例都能过还是 WA,我会回读一遍题面,确认是不是题意理解有偏差。这个过程比盲目打印中间变量要高效得多。
5. 复盘方法论:把一套题集合的价值榨干
5.1 模考后的24小时复盘法:趁热打铁,记忆最牢
模考结束后的24小时是复盘黄金期。刚做完的题目和思路还留在脑子里,这时候回顾,效率是最高的。我又一次把模板考的成绩和题目整理成了一个表格,专门记录:题目考察的算法、我的思路、卡住的地方、正确解法、代码实现。
复盘重点不在“把答案抄一遍”,而在“还原当时的思考过程”。我会问自己三个问题:为什么一开始没有往正确方向想?是漏看了某个条件还是这个算法不熟?下次遇到类似题目,第一步应该先判断什么?想清楚这几个问题,一套题才算真正吃透。
5.2 高频错误速查表:别让同一个坑埋你两次
我做题这些年,见过太多重复出现的错误。整理成一张表,你自己刷题的时候也可以对照着看:
| 错误表现 | 常见原因 | 解决手段 |
|---|---|---|
| 本地没问题,提交就WA | 输入解析错误或输出格式不符 | 重新检查输入格式,去除多余输出 |
| 大数据量超时 | 用了O(n^2)以上复杂度 | 根据数据范围设计更优算法 |
| 运行时RecursionError | 递归深度超限 | 改用迭代或调高递归深度限制 |
| 边界用例没过 | 没有处理空输入/极端值 | 写代码前先列出边界情况 |
| 样例过了,但WA | 题意理解有偏差 | 回读题面,检查是否漏看条件 |
这张表我每次刷题前都会扫一眼,不是因为记不住,而是提醒自己别在低级错误上浪费时间。笔试的罚时是按分钟算的,每犯一次低级错误,都是在挤压后面题目的时间。
5.3 刷题节奏建议:不以量取胜,以“覆盖度”为目标
最后说说刷题策略。很多人喜欢每天刷十几道题,刷完就过,然后发朋友圈打卡。这种刷法爽是爽,但对能力的提升非常有限。我更推荐按主题分类刷题:这周只做二分答案,下周只做动态规划。每个主题至少集中刷十道以上,做到看到题目就能条件反射地想到算法方向,才算过关。
模考的作用是检验覆盖度:哪些主题已经掌握了,哪些主题还是空白。五模这套题集合就是一个很好的检测工具。做完之后,不要急着做下一套,先花上几天时间把暴露出来的薄弱主题补强,再进入下一轮模拟。这样一圈一圈下来,每个考点都过了一遍筛子,笔试通过率自然会高很多。
我个人还有一个习惯,就是把每套模考里做错的题按“思路错误”和“细节错误”分开归类。如果是思路错误,说明这类算法还需要重修;如果是细节错误,说明代码熟练度不够。这两种错误的包侧重点完全不一样,混在一起复盘容易顾此失彼。把这个习惯坚持下来,你到后面会发现:想要保持稳定的笔试发挥,靠的不是刷题数量,而是对高频考点的系统性掌握。