
1. 这套题集到底值不值得刷——先说结论把时间拨回2019年那时候牛客网的模拟笔试还没像现在这样铺天盖地当时能遇到一套完整的“一模”编程题对准备校招的人来说就跟拿到押题卷差不多。这套题集是按牛客官方笔试标准出的一共好几道编程大题覆盖了字符串处理、数组与查找、模拟实现、动态规划这些笔试高频考点。我当时拿到这套题之后前后刷了两遍。第一遍限时做第二遍对照题解复盘第三遍自己重新实现一遍并优化。整个过程下来最大的感受就是这套题的难度梯度设计得非常合理前几道题是给大多数人送分的“基本功题”中间一两道是区分“会写代码”和“写得对”的人最后那道压轴题才是真正拉开差距的地方。所以它特别适合三类人第一类是准备参加校招笔试的应届生拿它提前适应考场节奏第二类是刚学完数据结构、想检验自己编码能力的同学它能帮你快速定位短板第三类是已经在工作、但想跳槽或者想找回刷题手感的人用这套低成本题集练手再合适不过。如果你正处在备战笔试的阶段把它当作一套模拟卷来做而不是零散地刷几道题效果会好很多。2. 整体拆解这套题的题型分布与考察逻辑2.1 常见的题型覆盖范围整套题从内容上看大致可以分成四类字符串与字符处理类、数组与排序查找类、模拟实现类、动态规划与数学规律类。这个分类不是我硬贴标签而是基于牛客这套题实际覆盖的考点整理出来的也是绝大多数互联网公司笔试编程题的通用分类方式。字符串类题目通常出现在第一或第二题的位置它对算法要求不高主要考察你对边界条件的敏感度和API的熟练程度。数组与查找类题目会涉及二分、双指针、排序变种属于算法基本功的检验。模拟类题目的代码量会明显增加很多同学不是不会做而是写着写着逻辑就乱了这种题考察的不只是思路更是工程化编码能力。动态规划和数学规律题放在最后用来区分思维深度这类题往往是整套卷子的关键得分点。2.2 难度梯度设计的逻辑我记得当时做这套题时最明显的感觉是前面的题几乎不用怎么思考写完就能过到了中间的题得停下来想一下边界到了最后一题真的会卡住一段时间。这种编排其实是刻意为之的。笔试时间有限前面放简单题是为了让你稳定拿分中间放中等题为的是筛掉基础不牢的人最后一题则留给真正刷过题、见过套路的人。所以你在刷这套题的时候千万不要只追求“AC了就行”而是要在每道题上多问自己一层这道题考的到底是哪个知识点有没有更优的做法我的解法和标准解法差距在哪。我当时第二遍刷这套题花了比第一遍多得多的时间但收获也大得多。3. 字符串处理类题目——边界条件才是真正的考点3.1 从一道典型题看字符串题的核心套路字符串题在笔试里是最容易“翻车”的一类因为看起来简单人人都能写但不是人人都能写对。比如统计一段字符串中每个字符出现的次数、判断一个字符串是不是回文串、将一个英文句子按单词反转输出这些题只要思路没错基本都能编出来但真正决定你能不能AC的是那些藏在角落里的边界条件。拿“按单词反转句子”来说核心思路无非是“先整体反转再按单词反转”但实际操作中问题一个接一个输入字符串首尾可能有空格两个单词之间可能有多个空格最后一个单词后面没有空格怎么办。很多同学第一遍写出来的代码在普通用例上跑得好好的一提交就报错就是因为没有处理这些边缘情况。3.2 我在做这类题时的处理模板我后来养成一个习惯做任何字符串题之前先在代码里把边界预处理写清楚。主要有几步去掉首尾空格、统一内部空格、判断是否为空串或长度是否合法。下面这段代码是我后来处理类似问题时常用的模板用Python写清晰很多def reverse_words(s: str) - str: # 第一步去掉首尾空格再按空白字符切分 words s.strip().split() # 第二步反转单词列表 words.reverse() # 第三步用单个空格拼接 return .join(words)这段代码能处理绝大多数边界情况因为strip()把首尾空格清掉了split()默认按任意连续空白字符切分所以多个空格也不会产生空串。我拿它和之前用双指针对字符串逐字符处理的写法对比过简洁程度高出一大截而且不容易出错。笔试场景下能简单就不要复杂这是血泪教训。3.3 字符串题的易错细节我踩过的坑中最典型的有几个。第一个是字符大小写问题很多题要求忽略大小写比如判断回文串不能用直接比较要先转换成统一格式再比。第二个是Unicode字符问题如果题目明确说是英文字符那没问题如果没明说最好用Python的isalnum()来处理不要手动判断字符范围。第三个是空串问题我见过不少同学在len(s) 0这个判断上栽跟头函数一进来不做空值检查直接下标访问直接抛异常。做字符串题还有一个经验就是宁可多写几行防御性代码也不要赌输入一定干净。我刷这套模拟题时的做法是每道题写完先跑几个特殊用例空字符串、只有空格的字符串、数字和字符混合的字符串、超长字符串。哪怕题目没有要求也会自己测一遍。4. 数组与查找类题目——别在“送分题”上摔跤4.1 典型的数组题思路拆解数组和查找类的题在整套模拟题里占据了至少两道的份额。其中最有代表性的一类是二分查找的变种比如在一个经过旋转的有序数组中查找目标值、在有序数组中找到第一个大于等于目标值的位置。这类题表面上是“找数”实质考察的是你对二分查找循环不变量——也就是每一步区间划分条件的理解。很多人写二分查找时习惯用一个模板然后到具体题目里直接套结果发现边界条件对不上。我见过最多的错误是死循环while left right时更新逻辑写错了导致区间不收缩。以旋转数组查找为例核心判断是nums[mid]和nums[right]的大小关系先确定哪一半是有序的再判断目标值落在哪一段。def search_rotated(nums, target): left, right 0, len(nums) - 1 while left right: mid (left right) // 2 if nums[mid] target: return mid if nums[left] nums[mid]: # 左半部分有序 if nums[left] target nums[mid]: right mid - 1 else: left mid 1 else: # 右半部分有序 if nums[mid] target nums[right]: left mid 1 else: right mid - 1 return -1这段代码里nums[left] nums[mid]这个等号特别关键漏掉等号会在只有两个元素的时候出错这是二分题里最经典的隐蔽Bug。4.2 排序类题目的隐藏陷阱数组题的另一类考法是排序变种比如要求O(n)时间内的部分排序、求第K大的数、合并两个有序数组。这类题会引导你去想快排的partition操作或者直接要求你手写堆排序。我做这套题的时候有个很深的体会排序题目本身不难难在时间复杂度的限制。如果题目要求O(n log n)那直接调内置sorted或者手写快排都行但如果题目要求O(n)或者不让用额外空间就需要引入计数排序、原地快排的partition这类技巧了。说到不用额外空间合并两个有序数组经典做法是从后往前填先把大元素放到nums1末尾避免从前向后插导致大量搬移。我刚学的时候不理解直到自己动手分析了一次时间复杂度才明白两者的差距这个思路值得每个准备笔试的人刻在脑子里。4.3 双指针与滑动窗口的取舍2019年的这套题里还出现了需要双指针或滑动窗口的场景典型如“找数组中和为某个值的连续子数组”或“最长的无重复字符子串”。这类题的核心思想就是把暴力枚举的O(n^2)降为O(n)靠的是右指针扩展、左指针收缩这个动态窗口机制。我在刷题时总结了一个判断标准凡是一个问题涉及“连续子数组”或“连续子串”并且要求最优解优先考虑滑动窗口或双指针如果子序列不要求连续再考虑动态规划。这个判断标准帮我节省了大量时间在考场上一个题能快速确定用哪类算法就已经赢了一半。5. 模拟实现题——代码量大不代表难度高5.1 拆解一道典型的模拟题模拟题是笔试里最容易让人“心态崩”的题。明明思路很直白就是按题目描述一步步做但代码写起来又臭又长稍不留神就多写或少写一个条件。这套模拟题里有一道典型题是模拟一个简易的文本编辑器操作要求处理插入、删除、撤销之类的命令。这种题难点不在算法而在你能否把所有可能操作都梳理清楚并设计一个清晰的状态模型。我记得当时自己第一遍做这道题时直接用一个列表来存文本然后按命令逐条处理。写完提交才发现漏了“撤销”操作和前面操作的联动关系改了好一会儿才通过全部用例。复盘时我才意识到应该先把所有命令类型枚举出来把每个操作对状态的影响画清楚再动手写代码。这和写工程代码是一个道理先设计再编码效率反而更高。5.2 模拟题的数据结构选择模拟题的实现质量很大程度取决于你对数据结构的选择。比如模拟调用栈、括号匹配用栈来做最直观模拟排队、消息队列用队列模拟键值存储用哈希表模拟矩阵操作直接用二维数组。选择对了代码量能少一半。我当时就在一道“实现一个支持括号嵌套的字符串解码器”的题上吃过亏一开始想着用递归来做代码写得复杂。后来换成栈的思路用两个栈分别存数字和字符串前缀边遍历边压栈遇到右括号就弹栈拼接十几行就搞定了。这也让我养成了一个习惯做模拟题之前先想一想“题目里的每个操作天然对应哪种数据结构”想明白再动键盘。5.3 如何在模拟题中保持代码清晰模拟题能不能拿满分关键看你能不能把状态维护清楚。我的经验是把每个状态的变换逻辑封装成独立函数避免在超长主循环里堆逻辑。比如一个文本编辑器的模拟插入、删除、撤销、重做各写一个函数每个函数只负责一件事主流程只做分发。这样即便出了问题单步调试也能很快定位。做这套题的时候我还特意给自己定了一条规矩一条命令处理完立刻写一行注释说明当前状态。虽然看起来多花了几秒但遇到复杂用例核对结果时效率不知道高了多少。6. 动态规划与数学规律题——真正的分水岭6.1 一眼识别DP题的特征整套模拟题的最后一道题往往不是常规的动态规划就是数学规律找规律题。动态规划题在笔试中非常好识别只要题目里出现“最多”“最少”“有多少种方式”“最大收益”这类字眼大概率就是动态规划。核心就三件事定义状态、写转移方程、定初始化和遍历顺序。以我遇到的一道典型题为例给定一个数组求不相邻元素能组成的最大和。这个问题在LeetCode上叫“打家劫舍”非常经典。定义dp[i]为前i个元素能得到的最大收益那么对第i个元素只有两种选择选它则收益为dp[i-2] nums[i]不选它则收益为dp[i-1]两者取大就是答案def rob(nums): if not nums: return 0 if len(nums) 1: return nums[0] dp [0] * len(nums) dp[0] nums[0] dp[1] max(nums[0], nums[1]) for i in range(2, len(nums)): dp[i] max(dp[i-1], dp[i-2] nums[i]) return dp[-1]6.2 空间优化的切入点在笔试中动态规划题如果能写出转移方程基本已经拿到了一大半分数但还有一个容易拿分的点——空间优化。以“打家劫舍”这题为例每次计算只需要用到前两个状态完全可以用两个变量滚动更新把O(n)的空间降为O(1)。当时我在刷这道题时一开始老老实实开了数组AC之后再看题解发现滚动变量更优雅就照着写了一遍。这里我特别想说一个细节笔试场景下先保证正确再考虑优化。很多同学一上来就想写空间优化的版本结果状态没理清楚反而写出Bug。我的建议是第一遍先写出最直白的数组版本确认逻辑没问题如果有余力再优化空间。6.3 数学规律题的破局思路与动态规划并列的压轴类型是数学规律题。这类题往往让求某个数列的第n项、某一串数字在这个序列中的位置、或者某个游戏局面是否必胜。这类题最怕的是思路跑偏直接按题意硬模拟结果时间超限。我的经验是先手动算前几个值看是否有递推规律然后大胆猜一个递推式用代码验证几个小数据。比如求一个游戏结果是否必胜先枚举几个小数据看看奇偶性、周期性或者模数关系很多时候答案就是一层窗户纸。如果手动推不出来就用暴力法先求解小范围数据然后观察输出找规律。做数学规律题最忌讳空想一定要动手算。7. 考场上最容易翻车的三个环节7.1 时间分配不合理我做这套模考的时候犯过最大的错误就是在前两道简单题上花太多时间总想写出“最优雅”的解法结果到了最后一道题只剩十几分钟。实际笔试时间是固定的简单题的价值在于稳定拿分不在于炫技。我的建议是先快速扫一遍所有题把简单题、中等题、难题在心理上分级先从“最有把握”的开始做最后再集中火力攻难题。我自己后来整理了一个时间分配策略如果整套题有6道编程题前两道预算30分钟中间两道预算50分钟最后一道难题预算40分钟剩下留作缓冲。遇到一道题超过20分钟没思路立刻跳过去做下一道千万不要在一棵树上吊死。牛客这套模拟题的题量不小时间规划直接决定你能不能把所有会做的题都做完。7.2 不测边界用例就提交考试时最亏的丢分方式不是题不会做而是题目明明会做却因为没测边界用例导致只过了一部分测试点。以我个人经验来说宁可少写几行代码也要把边界用例提前列出来。常用的边界用例有几类输入为空、输入长度为1、输入全相同、输入是最大数值、输入是负值、输入包含重复元素。对每一道题我都会在写完主体逻辑后快速跑一遍这些用例。比如做数组题时先拿一个空数组、单元素数组试一下做字符串题时先拿空串和全空格试一下。这个习惯让我避免了很多“AC率95%”的尴尬情况。牛客这类OJ判题时通常会把边界用例放在里面有时甚至专门有一两个测试点就是空输入跑出来结果不对才发现自己漏了判断。7.3 没有先人工验证样例再提交这也是新手很容易踩的坑代码写完样例都不跑直接提交然后等判题机打脸。正确的流程是先把题目给的示例输入喂给自己的代码人工对比输出对不对再额外跑几个自己想的用例最后才提交。我做这套题的过程中几乎每一道题都遇到过样例能过、但自己构造的用例不通过的情况可见这一步有多重要。另外一个细节是注意输入输出格式。牛客这套题很多要求从标准输入读取、按特定格式输出多一个空格、少一个换行都可能被判定为格式错误。做完题提交前我一般会仔细看一眼输出样例里的空格和标点确保完全一致。8. 复盘指南如何把一套题的价值榨干8.1 一题三做限时做、对照做、独立做刷完一套题真正的学习才刚开始。我自己常用的复盘方法叫做“一题三做”。第一遍是限时模拟考按考场标准来第二遍在看懂题解或讨论区思路后照着新思路重新做一遍第三遍隔个三五天不借助任何参考自己从头写一遍。做到第三遍还能流利写出来这道题才算真正消化了。这套2019牛客一模的题集里有几道题我在第一遍时完全没思路第二遍看了题解之后觉得“原来这么简单”但到了第三遍独立实现时又卡住了。这说明当时只是“看懂了”而不是“会做了”。所以千万不要以“看懂了”为标准来要求自己要能以“不看答案写出来”为标准。牛客的讨论区里一般会有很多种解法多翻翻不同思路比只看一个答案收益大得多。8.2 按知识点整理错题本做完这套题我会把错误的题和卡壳的题按知识点整理出来。比如字符串处理、二分查找、双指针、动态规划每个分类下面记下自己“卡在哪一步”“最终用什么技巧解决”。之后每周翻一遍错题本直到看到题目描述就能条件反射地写出核心代码为止。我的经验是笔试刷题最重要的不是数量而是每道题带来的思维增量。错题本的价值在于把“当时的思维盲区”记录下来下次考试前只需要翻一遍错题本就能快速回忆起自己最薄弱的地方针对性地补强。这套题集里如果发现自己二分边界总想不清楚就额外找同类型的题再练十道直到不再出错为止。8.3 用这套题评估自己当前的笔试水平把整套模考当作水平测试复盘后对自己做一个客观评估也很关键。我一般会用三个维度来判断正确率、用时、代码风格。正确率反映算法掌握程度用时反映熟练度代码风格反映工程素养。如果整套题能在限时内AC三到四道说明基础知识基本过关可以进入下一阶段——专攻难题和冷门考点如果只能AC一两道那就别急着刷难题先把基础题型练扎实。我自己在2019年做这套题时限时只完成了四道最后一道完全没做出来。三个月后再回头看已经能稳定AC五道最后一题也能写出解法。这种进步感是刷题最真实的反馈也是坚持下去的动力。9. 关于这套题集最后再分享一点个人体会每次有人问我“笔试怎么准备”我都会把这套2019牛客模考一模编程题拿出来当例子。它虽然不是最难的题集但胜在题型全面、难度递进合理非常适合作为摸底自测和训练材料。我后来面试时遇到的很多题目都能在这套题集里找到影子尤其是字符串处理、二分变种和动态规划这三类几乎是大厂笔试的必考项。我记得当时刷完这套题后最大的收获不是会做了几道题而是养成了“先想边界、再写代码、最后再测试”的习惯。这个习惯一直保留到现在写工程代码时同样受益。如果你正在准备笔试真心建议你把这套题当成一套标准模拟卷按考试流程完整做一遍然后认真复盘。磨刀不误砍柴工考前多花几天把这套题吃透比盲目刷几百道新题要有用得多。