公司动态
牛客模考四模刷题复盘:校招笔试算法考点与OJ避坑指南
从2019年到现在牛客模考四模这套题我前前后后翻过好几遍。第一次刷它的时候我正在准备暑期实习的笔试当时对着题目集合一道一道抠觉得有些题思路很绕后来真正走进校招笔试考场才发现模拟题里练过的东西几乎都派上了用场。很多人觉得“2019年的老题没什么用”但我的看法正好相反笔试算法题的考点迭代很慢题目会过时考法不会。这篇内容就围绕这套模考题把我自己刷题的经验、总结出来的考点规律、牛客OJ环境下的踩坑细节以及后来验证过的笔试答题策略完整写出来。不管你现在是刚开始刷题的在校生还是准备跳槽想找手感的老手这篇都能给你一点可落地的东西。1. 2019年的这套模拟题现在拿出来刷还值不值1.1 四模在整个模考序列里的位置牛客的模拟考试是一整个系列的从一模到后面的场次每一套的定位都不太一样。前面几套往往偏“试水”考查一些基础语法和简单数据结构目的是让刚接触笔试的人先熟悉牛客的在线判题系统感受一下什么叫做“提交代码后由机器判定结果”。而四模差不多处在一个难度逐渐爬坡的节点上它不会像后段模考那样刻意上难题也不会像前段那样放水整体命题风格更接近真实校招笔试的平均水平。这一点很关键如果你想找一个“难度对标真实笔试”的训练材料四模正好在甜点上。我当年刷四模的时候明显感觉它筛人的地方不在于题目本身有多偏而在于“你能否在有限时间内稳定地解出中等难度题”。它里面大量出现的是字符串处理、模拟流程、排序、双指针、基础的动态规划和贪心。这些考点单独拿出来都不稀奇但组合在一起再加上时间压力就成了区分度所在。1.2 题面会过时考点不会过时有人说2019年的题太老现在的笔试早就不考这些了。这句话只对了一半。确实现在的笔试题目越来越喜欢套“业务场景”比如给你一个日志分析需求、一个电商促销规则模拟甚至把题目包装成“设计一个推荐系统筛选逻辑”。但你把包装拆掉内核依然是那几类算法。四模的价值恰恰在于它的题目包装比较朴素能把算法内核看得一清二楚非常适合用来建立“识别考点”的能力。举个例子一道题描述说“某系统每天产生若干条日志需要按时间排序并去掉内容完全相同的重复日志”你看着像业务题其实考的就是排序加去重。四模里类似的朴素描述很多你在这上面练熟了到了正式笔试里面对再花哨的包装也能一眼看穿它到底在考什么。这就是老题新做的核心价值。2. 四模编程题里最常见的四类考点和解法模板2.1 字符串与模拟先把规则翻译成代码字符串处理在四模里出现频率相当高。不是因为字符串算法本身多难而是因为它最适合用来考“边界处理”和“规则理解”。常见的有字符串反转、按规则过滤字符、判断回文、简单的字符串匹配、字符出现次数统计。这类题的核心难点不在于算法而在于把题目要求逐字翻译成代码逻辑。很多人在笔试里翻车就是因为漏了规则里的某个修饰词。我记得这类题目里有个很典型的变体要求判断一个字符串能否通过删除一个字符变成回文串。如果你一上来就想着枚举删除哪一个字符然后判断时间复杂度会变成O(n²)字符串一长就超时。正确的做法是使用双指针从两头往中间扫描遇到左右字符不等时尝试删除左边那个或右边那个再分别判断剩余部分是否为回文。整个复杂度只有O(n)。用Python写出来大概是这样的def valid_palindrome(s): left, right 0, len(s) - 1 while left right: if s[left] s[right]: left 1 right - 1 else: # 尝试删除左边或右边的字符判断剩余子串是否为回文 return is_palindrome(s, left 1, right) or is_palindrome(s, left, right - 1) return True def is_palindrome(s, left, right): while left right: if s[left] ! s[right]: return False left 1 right - 1 return True这种双指针加分支判断的思路在四模的字符串题里反复出现。建议你把这套模板记熟它解决的不仅仅是回文删除问题很多类似“最多删除k个字符是否满足条件”的题目都能在这个基础上扩展。2.2 排序与双指针降低复杂度的核心套路排序是笔试的常客但单纯考“调用排序函数”的题目太没营养所以牛客这类模考更喜欢把排序当作预处理步骤配合双指针或二分来解决问题。最常见的两个套路是两数之和、三数之和、区间合并、求两个有序数组的交集。两数之和有很多变体。如果题目要求返回下标那可以用哈希表存数值到下标的映射一遍扫描完成时间复杂度O(n)。如果题目要求数组本身有序那就上双指针左指针从头部开始右指针从尾部开始根据当前和与目标值的大小关系移动指针。这个思路在四模里出现过不止一次我建议你两种写法都烂熟于心。区间合并也是高频考点。思路很固定先按区间起点排序然后遍历区间列表如果当前区间的起点小于等于上一个合并区间的终点就说明重叠了更新合并区间的终点否则把上一个合并区间收进结果列表开始新的合并。这类题练熟之后基本就是默写重点在于你要记住“先排序再逐个合并”这个流程。我刷四模时最大的感悟是排序题考的不是你能不能写对排序算法本身而是你能不能意识到“这题应该先排序”。这个意识需要通过大量刷题来培养遇到无序数组、求最大/最小/间隔/重叠之类的问题第一反应就应该是排序。2.3 动态规划与贪心学会判断题目在考哪一种动态规划和贪心是四模里区分度比较大的考点也是很多人最头疼的部分。其实两者之间有一个很实用的判断方法贪心是每一步做局部最优选择且不关心后续状态动态规划则是把问题拆成重叠子问题用状态转移来穷举所有可能并记录最优解。贪心类题目最典型的特征是“可证明局部最优能推出全局最优”。比如区间调度类的“选择最多不重叠区间”做法是按结束时间排序然后贪婪地选择结束时间最早、且与上一个选择不冲突的区间。这种题不需要dp数组代码很短真正难的是你在考场上敢不敢用贪心。动态规划则需要记住几个常见模型。四模里出现概率最高的是最长上升子序列、背包问题变体、编辑距离、以及二维网格路径问题。以最长上升子序列为例经典解法是O(n²)的dp几乎所有人都能写对。但笔试里数据范围如果给到10的5次方O(n²)就会超时你需要用贪心加二分的优化版本维护一个递增序列然后替换合适位置的元素。这个进阶版本的代码看起来和普通dp完全不同很容易记混建议专门做一次对比记忆。import bisect def length_of_lis(nums): tails [] for x in nums: pos bisect.bisect_left(tails, x) if pos len(tails): tails.append(x) else: tails[pos] x return len(tails)判断一道题该用dp还是贪心可以这样想如果题目问的是“最多”“最少”“是否可行”而且每一步选择都会影响后面的选择优先考虑dp如果每一步的最优选择都不需要回看前面状态优先考虑贪心。当然这个判断不是绝对精确但对笔试足够了。2.4 栈与单调栈括号和区间问题的万能武器栈在四模里的地位相当稳固尤其是括号匹配类的题目几乎每套模拟题都会出四模也不例外。括号匹配的经典解法是遇到左括号进栈遇到右括号检查栈顶是否匹配匹配则弹出不匹配则判错。这个思路看着简单但它衍生出的题目很多比如“最长有效括号子串长度”“括号匹配中的最小添加次数”“带通配符的括号匹配”。最长有效括号是一个值得研究的题它可以直接用栈来做始终保持栈底元素为当前已遍历的“最后一个未被匹配的右括号的下标”一开始入栈-1。扫描每个字符遇到左括号就把下标入栈遇到右括号就弹出栈顶然后计算当前下标与栈顶元素下标的差值更新最大值。如果弹出后栈为空说明当前右括号没有匹配把它入栈作为新基准。这个思路很巧妙理解之后你会对栈的应用有更深的感觉。单调栈在四模里出现得稍少但一旦出现就是拉开差距的题。经典的场景是求数组中每个元素右边第一个比它大的元素位置。做法是维护一个从栈底到栈顶递减的栈从右往左遍历数组每次把栈顶小于等于当前元素的值弹出剩下的栈顶就恰好是右边第一个更大的元素。这个套路背下来不难难的是识别出“这题其实可以用单调栈”。建议遇到“求左边/右边第一个更大/更小元素”的题优先想单调栈。3. 笔试的时间分配与做题顺序决定了你能否把会做的题都拿分3.1 先花三分钟通读全部题目很多人一进牛客的笔试页面就直奔第一题开始敲代码这是个非常亏的习惯。一套卷子里通常有两道到四道编程题题目难度并不一定按照顺序递增。我见过不少场次第一题反而是最麻烦的后面某道题反而简单得让人意外。你拿到卷子之后应该先花三分钟左右把所有编程题都通读一遍。把每道题的“数据范围”“输入输出格式”“大概考察哪个方向”记在心里给自己排一个优先级。这里有一个实用建议通读的时候不要急着想代码只做两件事一是判断每道题的难度二是估算每道题大致需要的解题时长。这样你后面做题的时候心态是完全不一样的。3.2 由易到难还是先做分值高的做题顺序本质上是一个取舍问题。我的原则是先做最有把握的题不管它在第几题再做中等难度的题最后再死磕难题。这么做的好处是你先把保底分拿满心态会稳很多。很多人在难题上卡了一个小时结果简单题没时间做这是笔试最亏的情况。时间分配上如果整套卷子编程时间一共90分钟我给自己的计划通常是题目类型计划用时说明简单题15-20分钟做完立刻检查边界争取一次通过中等题25-30分钟/道先想算法再动手避免写一半推倒重来难题剩余时间拿部分分优先于追求满分缓冲时间10分钟左右用于提交前的检查、救急调试这个时间表不是绝对的但它能保证你在90分钟里不会出现“最后一题明明会做却没时间写”的惨剧。拿到卷子先做时间规划是我刷四模之后养成的习惯后来在真实笔试里帮我稳住了局面。3.3 最后五分钟的抢救操作当剩余时间不足五分钟时不要开新题了。这时候最应该做的是把已经写完的代码重新检查一遍。重点查三个地方一是数组是否越界特别是循环里有没有i 1、n - 1这类可能在边界翻车的写法二是空输入的处理有些题目在n为0时需要特殊分支三是输出格式多了一个空格或者少了一个换行也会被判错。还有一个很多老手都在用的抢救技巧如果某道题你确实想不出正解但只要暴力法能过部分数据就把暴力版本写上并加上注释只要它不超时至少能拿一点部分的分数。牛客这类在线判题系统通常按通过测试用例的比例给分暴力法常常能拿到40%到60%的分这比空着强太多了。4. 牛客OJ环境下最容易丢分的五个细节4.1 输入输出规则不对代码全废在本地IDE里跑得欢快复制到牛客OJ上就报错这是新手很容易遇到的问题。牛客的在线笔试对输入输出有一套固定的约定最常见的坑有三种。第一多组输入。很多题目要求“输入包含多组测试用例每组占一行”你没写循环处理多组数据的话只能通过第一个用例。Python下标准写法是借助while加异常处理import sys def solve(line): # 核心逻辑 pass for line in sys.stdin: line line.strip() if not line: continue data list(map(int, line.split())) print(solve(data))或者用try...except EOFError的方式逐行读取效果一样。C和Java的同学需要特别注意cin和Scanner在有循环读取的情况下遇到EOF会自动终止但不要画蛇添足让程序提前break。第二输出格式。题目如果要求“每个结果占一行”你最后一行也要换行不要因为本地界面看不出区别就忽略它。如果要求“数字之间用空格分隔”每行末尾不要多出空格。这些看起来很琐碎但OJ的判定是逐字符比对多一个空格就是错。第三读入的字符串可能包含空格。如果用input().split()直接把一行读进去包含空格的字符串会被切碎。这时候要根据题目要求必要时先用sys.stdin.readline()读取整行再决定是否拆分。4.2 本地能跑、OJ报错的常见原因在牛客上刷题刷多了你会遇到一个很奇怪的现象同样的代码本地随便跑结果全对贴进OJ就报错甚至报运行时错误。这个问题我在四模练习时也遇到过原因通常出在以下几点。内存越界是C和C常见的问题尤其是动态规划二维数组的下标访问dp[i - 1][j]里的i - 1在i等于0时会越界本地编译器有时不报错OJ的编译器却会检查出来。解决办法很简单把dp数组多开一两行或者把i 0的情况提前特判。死循环也很常见。很多人在写while循环的时候忘记更新循环变量或者更新条件写错导致循环无法终止。本地跑的时候如果数据量小看不出问题OJ一旦跑超时系统就会无情地判你超时。所以每次提交前盯着while循环看一眼“有没有退出条件”。Python特有的问题是递归深度限制。如果你用递归实现DFS而树的深度在1万层以上Python默认的递归深度上限是1000直接栈溢出。要么增加sys.setrecursionlimit()要么改成迭代加栈。四模里有一类树的遍历题我第一次写的时候没注意这个吃了一次亏。4.3 性能与语言选择的权衡牛客OJ对运行时间有严格限制常见的是1秒或2秒。Python在这种场景下其实有点吃亏同样一个O(n²)算法用C可能能过用Python几乎一定超时。所以刷四模这类题的时候我建议你对自己的语言要有清楚的认知。Python的优势是写起来快、不容易在语法上出错适合处理字符串、模拟类题目。但如果你面对的是一道需要大量循环的题而且数据范围给到10的5次方以上Python的纯循环会非常吃力。这时候有几个优化手段把能合并的列表操作合并掉尽量使用内置函数避免在循环里频繁调用自定义函数能用NumPy的场景考虑用数组批量操作。但说到底笔试的时候不能指望语言优化来救命真正的解法应该是把O(n²)优化到O(n log n)这才是王道。如果你同时会写Java和C在牛客这种OJ上遇到复杂度吃紧的题直接用C是最稳妥的。我的习惯是简单题用Python中高难度且数据量大的题优先用C。语言切换本身需要熟练度建议平时刷题就固定这一套策略别到考场上临时换。5. 刷完模考之后的重头戏复盘和错题管理5.1 四类错题四种不同的处理方式模考成绩出来之后很多人只看一眼总分就过去了这是最浪费的做法。四模这套题我刷完第一遍回头整理错题时发现错误里大概能分成四类每一类的处理方式完全不同。第一类真的不会连思路都没有。这种题对应的算法专题你没掌握需要回到基础去学。比如你不会做最长上升子序列大概率是动态规划的状态定义还没形成习惯。这类错题不要急着看题解先去复习对应专题的基础课或者入门文章再回来做。第二类有思路但某个关键步骤卡住了。这种是最可惜的也最容易被题解“剧透”后产生虚假的学会了的感觉。我的建议是不直接看完整题解只瞄一眼关键提示比如“用单调栈”然后合上题解自己把代码写完整。第三类思路对但代码有bug。这种错题说明你的代码实现能力还有待提高具体原因可能是边界处理、循环条件、变量初始化。处理方式是最笨也最有效的手动在纸上跑一遍自己的代码找出报错逻辑。第四类实现完全正确但超时了。这类错题代表你需要学习更优的算法或者优化代码的常数。处理方式是看题解里复杂度更优的解法理解“为什么能优化到这种复杂度”然后把优化版本默写一遍。5.2 一套能落地的复盘流程很多人问复盘到底怎么复我分享一个自己摸索出来的流程你可以直接照搬。第一步做完全套题之后先不看题解把每道题按“会做且通过”“会做但超时”“有思路但没写出”“完全没思路”四档做标记。第二步对“会做但超时”和“有思路但没写出”的题给自己十五分钟再独立尝试一次不允许看题解只允许查语法和库函数。第三步十五分钟后依然做不出来再看题解。看的时候不要只看代码重点看解题思路和复杂度分析理解状态定义从哪来、转移为什么这样写。第四步把题目封装进你的错题本。我的做法是建一个表格记录题目来源、考点、错因、正确解法、复刷状态。格式大概是这样题目考点错因类型解法一句话总结复刷日期删除字符成回文双指针想复杂了双指针遇不同字符尝试跳过再判断已复刷第五步隔三天到五天把错题本里“复刷日期”为空或者标记为失败的同专题题重新做一遍。这个“间隔复刷”非常关键它能帮你判断自己是真懂了还是背会了。复盘这件事比多刷十道新题都管用。我自己的感受是四模的错题我反复刷了三四遍之后很多类似的考点后来在真实笔试里见到我基本是秒出思路。这个受益程度是单纯刷题量给不了的。6. 从四模出发一条更省力的刷题路线6.1 专题刷题优先于盲目题海如果现在让我给准备校招笔试的人规划刷题顺序我绝对不会让你一上来就疯狂刷整套模拟题。比较高效的路子是先按专题刷透再进入整套题模拟阶段。专题顺序建议是这样的先刷数组和字符串这类基础题把语言用法和常见字符串API练熟然后刷排序和双指针理解“有序数组的解题利器”接着刷栈和队列覆盖括号匹配和单调栈再刷二叉树掌握DFS、BFS、层序遍历这些树的常规操作之后再上动态规划和贪心这一部分需要的时间最多最后再回到模拟题里去检验。四模这套题以及牛客题库里的历年真题应该放在专题刷完之后作为“模拟考”来使用。如果你连专题都没刷完就直接做整套题很容易出现“每道题都眼熟、每道题都做不出来”的状态那种挫败感完全没有必要经历。6.2 从模拟题到真题的过渡方法当你把四模这类模拟题刷到稳定通过之后就要开始接触真正的校招真题了。牛客上最大的优势就是题库里沉淀了大量企业的真实笔试题目包括各大厂历年秋招春招的编程题。这些题比模拟题更难题目的表达也更晦涩有时你看完题面还不知道它要你做什么。从模拟题过渡到真题我的方法是先做“按公司维度”的真题积累不同公司的出题风格再做“按年份维度”的真题感受同一家公司选择题库的稳定性最后回到模拟题通过模拟考试来训练考场心态和时间分配。这个过程会有些漫长但你别指望一步到位刷题本来就是螺旋上升的。四模在这里面扮演的角色是“基础训练”的压轴环节。它帮你确认自己是否已经具备上真实考场的基础能力如果四模的题你都能在规定时间内解出来那真题只是需要适应题面的复杂度核心算法你都已经覆盖到了。6.3 基础入门者的衔接建议如果你点进这篇内容但觉得自己基础比较薄弱甚至还在背基本语法那我建议你先别直接碰四模。先把一门语言的语法基础补扎实再刷两三周牛客的“基础算法入门题”比如数组遍历、字符串翻转、基础排序。等这些题量达到五十道以上再回来感受一下四模的难度。Python是目前很多笔试的首选语言尤其对于非科班或者算法基础一般的人它的表达效率能省下大量时间。网上有很多Python入门练手题目包括一些等级考试的基础题目都可以拿来练语言熟练度。我的经验是先把基本语法和常用数据结构用熟达到“想到一个逻辑能顺手翻译成代码”的程度再进入算法专题训练。不然你会陷入一个恶性循环算法没学透代码也总是写不对最后两边都塌方。我自己的体会是笔试这件事最怕的不是难度而是没有章法。四模这套题最大的礼物是它在难度和真实考场之间搭了一架桥。你在上面认认真真走一遍后续刷真题、上考场都会从容很多。如果你也想在牛客上把刷题这件事做扎实把这套2019年的模考题翻出来做一遍再按照上面说的复盘方法走一遍一定不会亏。