牛客网2018一模编程题刷题攻略:从题型解析到笔试实战
1. 这套模考题是什么,适合谁来刷
1.1 2018年牛客一模的背景与价值
如果你在校招季前逛过牛客网,对“模考”这个词应该不陌生。牛客的模考(一模)系列,可以理解为正式笔试前的全真模拟——题目风格、难度分布、输入输出格式都尽量贴近当年各大厂笔试的真实氛围。2018年这套编程题集合(A),现在回看依然有很强的参考价值,因为它的题型分布非常经典:字符串处理、数组操作、简单模拟、基础数学计算占了绝大部分,几乎没有偏题怪题,属于“稳扎稳打型”的试卷。
我当时刷这套题的第一感受是:它不考你多高深的算法,不搞线段树、不搞网络流、不搞后缀自动机,考的全是“基本功”。但恰恰是这种基本功,在真正笔试的时候最容易翻车。为什么?因为几十个同学同时交卷,编译不过的直接零分,超时的直接淘汰,边界情况没处理的扣掉大半用例。这套模考卷的意义,就是帮你提前暴露这些问题,让你在真正的笔试之前把低级失误清干净。
所以这套题适合谁?我总结下来有三类人。第一类是正在准备校招的应届生,尤其是目标岗位是后端、前端、测试开发这类要考编程题的同学;第二类是刚学完数据结构、想检验自己编码基本功的在校生;第三类是工作几年后想跳槽,需要重新捡起刷题手感的人。如果你是这三类中的一员,这套卷子花两个周末过一遍,性价比非常高。
1.2 别等简历被刷才想起来刷题
很多同学有个误区:觉得刷题是临近笔试才做的事,简历投出去等通知的时候才开始慌。实际上牛客模考这类东西,最好的刷法是在投简历之前就做一遍。因为你做完一套,你大概就知道自己现在是什么水平——是能在45分钟内AC两道题,还是憋了一个小时连输出格式都没搞对。这个自我评估比任何模拟面试都诚实。
另外,这套卷子虽然标着2018年,但它的题目质量并不过时。编程题的考察内核一直很稳定:读题能力、边界处理能力、基础数据结构的应用能力。现在笔试虽然偶尔会冒出一些新玩法,比如交互式题目、系统设计题,但大部分公司的技术笔试,仍然以这类传统编程题为主。把2018年的卷子吃透,再去做近两年的真题,你会发现很多思路是通用的。我当时就是先刷了这套一模,再去做当年的秋招真题,明显感觉心理上有底了。
2. 题型分布与考察重点拆解
2.1 高频题型:字符串、数组、模拟是老三样
2018牛客一模(A)的题型分布,和当年大部分公司笔试的出题思路高度一致。我大概统计了一下,整套卷子的编程题可以分为三类:
- 字符串类:约占三分之一。常见考法有字符串翻转、子串统计、字符去重、简单模式匹配。难点不在算法,而在边界判断——比如空串、全空格、大小写混合、末尾换行符处理。
- 数组与数学计算类:约占三分之一。常见考法有求最大最小值、数组去重、累加累乘、质数判断、进制转换。这些题本质上是考你对循环和分支的掌控力。
- 模拟类:约占三分之一。给你一段业务规则,让你按要求一步步操作。这类题不考算法技巧,考的是“把自然语言翻译成代码”的能力。翻译得准不准,决定了你能过多少测试用例。
这个分布比例在很多公司的笔试卷里都能看到,甚至可以说是一套“保守但有效”的筛选逻辑:字符串考细心,数组考逻辑,模拟考理解能力。三者都不需要太高深的算法知识,但对代码基本功的要求很实在。如果你能把这三类题稳定地在30分钟内做出两道以上,你已经超过了相当一部分笔试候选人。
2.2 难度梯度设计:低中高题怎么拉开区分度
这套卷子的题目排序也有讲究,整体难度是逐步爬升的。前面的题基本上是“送分题”,只要思路对,代码量很少,10到15行就能搞定。中段的题开始加码,有了输入范围限制,比如数据量到10的5次方,要求你用O(n log n)的算法,这时候暴力解法就会超时。最后面的题往往是“纸老虎”——题目描述很长,场景很复杂,但拆解之后核心逻辑并不复杂,考验的是你在有限时间内快速理解规则并落地代码的能力。
我当时做完后复盘,发现一个有趣的规律:拉开分数差距的往往不是最后那道难题,而是中间的“中等题”。因为简单题大家都会,难题大家都做不出来,中等题的正确率才是区分度最大的区域。这套卷子里的中等题,恰好是那种“你觉得自己会,但一提交就有一两个用例过不去”的类型。所以刷这套题的时候,不要把精力全耗在最后一道题上,中段题才是拿分的关键区域。
我建议刷题时这样分配时间:前20分钟扫一遍所有题目,按难度打个标记;先稳拿两道简单题,再攻中等题;最后剩的时间再看难题有没有可拿的部分分。这个策略听起来老套,但在真实笔试里非常管用。
3. 典型题目思路还原
3.1 字符串类基础题:别小看翻转和去重
我记得这套卷子里有一道字符串翻转题,不是简单的逆序输出,而是要求翻转单词顺序,比如输入 “I am a student.”,输出 “student. a am I”。这道题属于典型的“一看就会,一写就错”。
直白的思路是:先对整个字符串做一次整体翻转,再对每个单词做局部翻转。整体翻转后变成 “.tneduts a ma I”,然后按空格切分,对每个单词再翻转一次,就能得到目标结果。这里有一个关键点:切分字符串的时候,不同语言的处理方式不一样。用C++的话要自己处理空格间隔,用Python的话直接split()很省事,但要注意多个空格连续出现的情况。用Java的话,String是不可变的,操作起来会生成大量中间对象,刷题阶段不推荐,但也不是不能做。
我当时用Python实现的时候踩过一个坑:用split(' ')和split()的结果不一样。前者会保留空字符串元素,后者会自动忽略连续空格。这个细节在本地跑代码的时候不容易发现,但牛客的判题系统用例覆盖得比较全,一旦用例里有连续空格,split(' ')就会输出错误结果。这类题目考的不是“会不会翻转”,而是“有没有处理特殊输入的意识”。我后来总结出一个习惯:凡是涉及字符串拆分的题,默认先想清楚分隔符有多复杂——多个空格、制表符、首尾空格,这些都要手动画出来测试一下。
3.2 数组与数学计算:边界条件是分水岭
数组类题目里,让我印象很深的一道是“求数组中的第k大元素”。这道题在LeetCode上是中等题,但牛客模考里它的输入描述做了些变化:第一行输入n和k,第二行输入n个整数,要求输出第k大的数。n的范围给到了10的5次方,意味着你不能用那种每次删除最大值的蠢办法。
常见的解法有几种:第一种,用堆,维护一个大小为k的小根堆,遍历一遍数组,堆顶就是第k大的数。时间复杂度O(n log k),空间O(k)。第二种,快速选择(Quick Select),平均复杂度O(n),最坏O(n²),但通常跑得快。第三种,直接排序后按下标取值,复杂度O(n log n),虽然理论上可行,但如果你的排序写得不稳,也容易超时。
我在刷这道题时的体会是:边界条件比算法本身更容易让你丢分。比如,k=1的时候,小根堆的大小是1,直接比较就行;k=n的时候,其实求的是最小值,你的代码要能处理这种极端输入。还有一个细节:输入是否允许重复数字?如果允许重复,那么“第k大”的定义是什么?是去重后的第k大,还是不去重的第k大?这道题的描述里没有特别说明,但我试了两种理解,发现牛客的用例是按不去重处理的。这个要靠读题经验——如果题目没有明确说“去重”,默认就是不去重。
另外还有一个数学类的经典题:给定一个正整数n,判断它是否是质数。这道题看着简单,但考查的是你对时间复杂度敏感度的判断。如果你老老实实从2遍历到n,遇到10的12次方级别的输入,铁定超时。正确做法是遍历到 sqrt(n),并且可以先排除偶数和2,进一步减少一半计算量。我当时把判断质数封装成了一个函数,这套模考题后面有两三道题都复用了这个函数,节省了不少时间。
3.3 模拟题:把题目翻译成代码的能力
模拟题是这套卷子里最需要耐心的一类。我记得有一道题描述了一个复杂的业务场景——具体来说是关于排队叫号的过程,有A、B、C三种客户类型,每种类型的办理时长不同,客户按到达时间排队,但有VIP客户可以插队,要求输出每个窗口处理的客户列表。这道题一看描述就吓人,实际上核心逻辑就是维护一个优先级队列,以及若干个普通队列。
我当时拿到这种题,习惯性的处理方式是三步走。第一步,先把题目里的规则用自己的话复述一遍,写到草稿纸上,确认自己理解了规则而不是似懂非懂。第二步,找出题目里的“实体”和“动作”,实体是客户、窗口、队列;动作是到达、排队、办理、离开。第三步,为每个实体设计数据结构。这一步想清楚了,代码就是顺着流程往下写。
这道题真正难的不是数据结构,而是“VIP插队”这个规则的处理。如果先到先得队列和VIP队列分别维护,什么时候把VIP队列的人插入到普通队列前面?是在新客户到达的时候,还是窗口空闲的时候?题目描述可能只说“VIP优先”,但不告诉你具体是哪种触发时机。这种情况下,我建议先按“窗口空闲时优先从VIP队列取人”来实现,如果用例没过再调整。因为窗口空闲时检查VIP队列,逻辑最简单,也最容易排查问题。这道题做完,我对模拟题的心态发生了一些变化——不再怕长题目了,反而觉得长题目信息量大,反而容易从描述里找到提示。
4. 笔试现场的时间分配与实战策略
4.1 先读题后动笔,优先级排序方法
在牛客这种OJ系统上做笔试,时间管理的重要性甚至超过技术能力。我见过太多同学,拿到卷子就闷头做第一题,做完一道就花掉了40分钟,结果后面明明有更简单的题却没时间写。真实的校招笔试两个小时三道题,是常态。这套模考卷也差不多,所以一定要养成先读题的习惯。
我的做法是:前10分钟不做任何代码,把所有题目都看一遍。一边看,一边给每道题打三档标记——“稳拿”的题是那种描述很短、事件逻辑清晰、你立刻能想出完整解法的题;“需要想一想”的题是有思路但边界情况需要仔细处理的题;“没头绪”的题是那种看完两眼发直的题(通常也是分值最高或描述最长的题)。然后按“稳拿优先”的顺序作答,先保证有两道AC在握,再回头啃需要想的题。
用这套模考题来说,字符串翻转、判断质数这类题属于“稳拿”档,应该优先写。排队叫号那类模拟题属于“需要想一想”档,放在第二顺位。如果最后还剩时间,再去看“没头绪”的题,哪怕只能过部分测试用例,也能拿一些分。牛客的判题通常按测试用例数量给分,部分通过好过交白卷。
4.2 语言选择与调试习惯
关于笔试用什么语言,我的观点比较明确:用你最熟悉、最不费脑的语言。很多同学纠结“Java还是C++还是Python”,其实在牛客笔试里,三种语言都可以提交,没有绝对的优劣。关键在于,考场上你需要的是肌肉记忆——比如用Python写遍历、用C++写STL容器,脑子里不需要额外思考就能敲出来。我自己笔试主用C++,因为刷题时用得多;但遇到字符串处理比较多的题,我偶尔会切Python,因为处理起来更省事。切换语言的前提是两种都用得熟,如果你只是半吊子,那还不如一门语言用到底。
调试方面,我的经验是:不要依赖在线调试器(实际上很多笔试环境根本不给调试器)。你要学会给自己写“肉眼可见的调试输出”——在关键分支打印中间变量,确认数据流转是否符合预期。但这里有个细节:提交前必须把所有调试输出注释掉或删除。我见过不止一个同学,代码逻辑完全正确,就因为多打印了一行中间结果,导致输出格式和预期不符,被判零分。这是一个非常可惜的低级失误,建议你在刷模考题的时候就养成习惯:写完代码后,先检查一遍有没有多余的打印语句,再点击提交。
还有一个习惯是“提交不着急”。牛客模考的判题反馈很快,但不要用“提交-看结果-再改”的方式来排除错误。正确做法是,先在本地想清楚所有测试用例,包括边界条件,自己构造几个极端输入跑一遍,确认无误后再提交。这样一次AC率会高很多,也更能模拟真实笔试时的心态。
5. 刷题过程中最常见的坑
5.1 边界条件和数据范围
我统计了一下,我刷这套模考卷时,将近70%的WA(Wrong Answer)都出在边界条件上。这不是我一个人的问题,几乎每个刷牛客的同学都会遇到。
最典型的几个边界条件包括:输入为空、输入为一个元素、输入为最大值、输入全是相同值、输入已经有序或完全逆序。这些情况在样例测试里很少出现,但判题系统的用例会覆盖。拿“判断质数”这道题来说,n=2和n=3是质数,n=1和n=0不是质数,n是负数也要考虑。有些同学直接忽略负数情况,虽然题目可能限定为正整数,但万一描述里写得模棱两可,你就有风险。
我在刷题时养成了一个强制习惯:每道题写完核心逻辑后,强制列举三个边界输入来测试。第一个是“最小合法的输入”,比如数组长度为1;第二个是“最大范围的输入”,比如n=10的9次方或字符串长度为10000;第三个是“带有重复或极端字符的输入”,比如字符串里全是空格或全是同一个字符。这三组用例跑通了,再提交,AC的概率会大幅提升。
5.2 输出格式和空行问题
输出格式是另一个容易阴沟翻船的地方。牛客的判题对空格、换行、大小写要求很严格,有时候多一个空格都判错。尤其是“输出所有符合条件的元素,空格分隔”这类描述,很多同学会在最后一个元素后面也打个空格,这在部分OJ里没问题,但在牛客的某些题里会被判错。
更隐蔽的是空行问题。有些题要求每组输出之间用一个空行隔开,最后一组之后没有空行;有些题要求每行输出后都没有多余空格。如果你在本地IDE里看不出来,可以把输出重定向到文件,用十六进制查看末尾有没有奇怪的字符。当然,刷模拟题的时候不需要这么较真,但你要有意识地保持代码整洁——用循环变量判断当前是不是最后一个输出项,是的话就不打分隔符。
我记得有一次我刷这套卷子里的数组题,本地样例输出完全正确,但提交后有一个用例失败。排查了半天,发现是题目要求“如果结果为空,输出空行”,我没有处理这个分支。这种用例虽然不是核心逻辑,但一旦出了就是用例不过。从那以后,我在读题时会特别留意“空结果”这种情况的处理要求。
5.3 超时与复杂度优化
牛客笔试的超时限制一般是1到2秒,如果你的算法复杂度太高,即使答案正确也会被TLE(Time Limit Exceeded)。这套模考卷里,数据范围比较大的题目集中在数组类上,比如前面提到的第k大。
很多同学在初学阶段习惯用暴力解法——双重循环嵌套,一时写起来很爽,但数据量一大就原形毕露。我建议在动手写代码前,先大概估算一下时间复杂度:如果你看到n是10的5次方,你的双重循环就是10的10次方次操作,在1秒内基本不可能完成;这时候哪怕要用写的代码多一点,也得换思路。正所谓“暴力一时爽,提交火葬场”,这个教训是我刷牛客刷出来的。
还有一个常被忽略的点:输入输出效率。用Python写笔试的时候,如果数据量大,print和input的频繁调用也会影响性能。这种情况下,建议把所有输出先放到一个list里,最后统一用'\n'.join()输出;输入则用sys.stdin.read()一次性读进来再按行处理。C++选手则可以考虑用ios::sync_with_stdio(false)和cin.tie(0)来加速输入输出。这些细节在数据量小的时候看不出来,但数据量一上来,可能就是1秒和1.5秒的区别。
6. 从模考到校招:我的个人经验
6.1 复盘比刷题更重要
做完这套模考题,最重要的一步不是看自己AC了几道,而是系统地复盘。我的做法是建立一个简单的表格,列出每道题的题型、耗时、是否AC、WA原因、TLE原因、最优复杂度。然后横向对比找出自己的规律——比如我发现自己的WA原因集中在边界条件,因为我在写循环时经常忘记处理首尾元素;另一类问题是“读题过快,理解偏了”,比如把“第k大”理解成了“第k小”。这些问题在复盘之后变得非常清晰,下次再遇到同类场景时,我会本能地多看一眼题目描述里的关键词。
关于要不要看题解,我的原则是:一道题如果想了30分钟还没有任何可行的优化方向,再看题解。但不能只看一份题解,至少要比较两种不同解法,比如堆和快排都可以解决第k大问题,两种都写一遍,感受它们在不同数据下的表现。只抄一遍题解是最低效的学习方式,因为那不是你的思路,你只是把别人的答案从屏幕上搬到了你的代码里。
6.2 从A卷到B卷:后续还可以怎么扩展
2018年牛客模考(一模)不仅有A卷,还有B卷和其他场次。我的建议是,A卷做扎实之后,把B卷也刷了。两套卷子对比着看,你会发现出题人的偏好——哪些知识点反复出现,哪些坑是故意设计的。这种对比性训练,比单刷一套卷子更有效。
另外,这套卷子的主题还可以扩展方向:把普通数组题替换成链表、二叉树版本;把字符串翻转替换成自定义分隔符的切分;把排队叫号模拟题换成更复杂的状态机场景。牛客网每年都有新的笔试题和真题集,你可以顺着这套模考的题型,去刷对应知识点在近两年的真题变化。比如2018年的char数组题,现在可能考的是字符串匹配或回文串;以前考模拟排队,现在可能考LRU缓存设计。技术点的内核没有变,但场景和数据结构复杂度在升级。
我个人最大的感受是:牛客模考的价值不是“押题”,而是让你提前进入笔试状态。很多东西你明明知道,但在时间压力下就容易犯蠢。比如忘记初始化变量、在循环里改变了迭代器的值、字符串比较用了==而不是equals(Java选手的血泪史)。这些低级错误,平时慢慢写根本不会犯,但考场上就是会发生。刷模考,就是让你在低成本环境下把这些错误全部暴露一遍,然后带着这些“免疫记忆”走进真正的笔试考场。
最后说一个我在刷完这套卷子后调整的心态:不要把AC率看得太重,把“能从每次提交失败中学到什么”看得更重一点。这套题让我进步最大的,是我终于养成了“先写测试用例,再写核心逻辑”的习惯,以及“写完代码后立刻用极端数据压测”的条件反射。这些东西,后来我写工程代码时也一直在用,受益匪浅。
