
好久没在论坛里正经写点技术复盘了。前两天整理网盘资料翻到一份2019年牛客模拟考试第四场四模的编程题合集顺手从头到尾重刷了一遍。老实说虽然是几年前的题但里面的考点设计——数组处理、动态规划、字符串操作、贪心模拟——放到今天的笔试场上依然是主力题型甚至有不少大厂面试还在用同款思路换皮出题。这篇文章我就把这套题集的题型布局、核心考点、每道典型题的完整解法和考场上的踩坑记录都拆开聊聊适合正在准备校招笔试、想刷牛客模考题找手感或者打算系统过一遍基础算法的朋友参考。先说结论2019牛客四模的编程题难度放在当年属于中等偏上但今天看来它的价值不在题目本身而在它覆盖的考点组合和边界条件设计非常能暴露代码习惯上的毛病。如果你只想刷难题这套题可能不够刺激但如果你想在笔试前做一次全面自检它比随机刷几十道题都高效。1. 题目布局与考点分布四模到底考了什么先给没参加过牛客模考的朋友科普一下背景。牛客模考是牛客网定期组织的在线模拟笔试题目风格对标当年互联网公司的校招笔试题通常包含选择题加编程题两部分。2019年这第四场编程题一共几道我记得不太精确了但从合集内容看核心集中在六类字符串处理与模拟数组操作与双指针排序与贪心动态规划基础数学规律推导图论或搜索的简单应用这个考点分布其实很有讲究。你看现在的笔试题目很少会单独考某一个算法基本都是两个知识点糅在一起比如“排序双指针”“字符串模拟哈希表”。2019年四模已经开始有这种趋势题目本身不算特别难但如果你只会背模板遇到变体就会卡壳。四模还有一个特色它对输入输出的边界处理要求很高。比如字符串题会混入空字符串和超长字符串数组题会刻意在数据范围上做文章逼着你去考虑时间复杂度和整数溢出。这些设计放在今天依然是笔试的核心考察点——机器判题时差一个边界条件就是0分和满分的区别。我当时刷这套题最大的感受是它特别像大厂的“筛人题”——不考偏题怪题考的是你有没有扎实的基础编码能力。如果你能在90分钟内稳定AC三道题说明你的基本功是过关的如果只能AC一道那大概率不是算法思路的问题而是代码实现不够干净。1.1 题目难度梯度设计每道编程题的难度并不是平铺的而是有明显的阶梯感。我重刷时给这套题做了一个粗略的难度分层按照题目的数据规模和算法要求划分难度层级对应题量典型特征考点关键词入门送分题1-2道题干短数据范围小直接模拟即可字符串、基础数学中等拉开差距题2-3道需要优化思路O(n^2)会超时双指针、贪心、排序压轴筛选题1道综合性强状态设计与边界细节多动态规划、记忆化搜索这个梯度设计其实很科学。入门题保证大部分人能AC不至于上来就心态崩中等题开始区分有没有刷题量压轴题则是给真正准备充分的人准备的。模考分数出来之后你会发现分数分布基本呈正态分布大部分人卡在中等题上。我建议刷这套题时不要只关注AC数量而是关注自己卡在哪一层。如果能稳定AC中等题笔试已经有了基本的竞争力压轴题如果半小时内没思路果断放弃去检查前面的题性价比更高。1.2 牛客OJ的判题机制与应对策略做题之前先花一分钟说说牛客的判题机制因为很多人在本地IDE用标准输入输出没问题一提交就报错大概率就是没搞懂判题环境的规则。牛客的编程题接口一般分为两种核心代码模式和ACM模式。核心代码模式是LeetCode那种你只需要实现一个函数ACM模式是你需要自己处理输入输出从标准流读取数据再打印结果。2019年四模的编程题是纯ACM模式这在当年是主流现在很多大厂笔试也在用这一点来筛选候选人的工程能力。别小看输入输出处理我见过不少人思路完全正确结果因为读取多行输入时漏了空行处理直接整题0分。应对ACM模式有几个基本建议用sys.stdin.read()一次性读入所有数据再split效率比逐行input()高很多尤其是数据量大的场景。输出时统一用列表收集结果再一次性打印不要每算一个结果就print一次否则几百个case时容易拖慢整体速度。注意题目给出的输入范围如果数字可能很大Python可以用原生的int但如果是C选手要用long long。这些细节听起来琐碎但恰恰是笔试中最容易扣分的点。刷2019四模这类ACM模式题集正好可以帮你把这些习惯养成本能。2. 核心考点拆解每种题型背后的通用套路做完整套题后我最大的体会是编程题考的不是你会不会某个算法而是你能不能在一道题干看似复杂的题里快速识别出它底层在考什么。四模的题目虽然都是三年前的但考点设计得相当典型非常值得逐个拆解。下面挑四个出现频率最高的方向详细说说。2.1 字符串处理与模拟先拆解再编码字符串处理题几乎每场模考都会出现四模也不例外。这类题看着简单实际上是最容易翻车的题型因为字符串的操作太灵活了——反转、切片、大小写转换、去重、模式匹配每一种都有不同的坑。我重刷时遇到的一道字符串题大意是给定一个由大小写字母组成的字符串要求统计连续相同字符的最大长度并输出这个长度和对应字符。题目描述非常直白但测试用例里有一个小陷阱字符串可能全部由同一个字符组成也可能只有一个字符甚至可能是空字符串。解题思路其实很常规一次线性扫描就能搞定def solve(s: str) - str: if not s: return 0 max_len 1 cur_len 1 max_char s[0] for i in range(1, len(s)): if s[i] s[i - 1]: cur_len 1 else: if cur_len max_len: max_len cur_len max_char s[i - 1] cur_len 1 if cur_len max_len: max_len cur_len max_char s[-1] return f{max_char} {max_len} print(solve(input().strip()))这个解法的时间复杂度是O(n)空间复杂度O(1)已经是这道题的最优解。但真正让我想强调的是编码之外的步骤——先拆解再编码。我看到很多人一上来就写循环写着写着漏掉了“连续”这个条件或者最后忘记更新最大字符。实际上字符串模拟题最稳的做法是先理清几个关键点输入会不会有空格需不需要strip字符比较是区分大小写还是不区分统计条件是“连续相同”还是“全局出现次数”输出格式要求是什么空格位置对不对这些点想清楚再动手编码速度反而更快。因为模拟题考的不是算法高深程度而是严谨程度。你有多少边界case没考虑到系统就有多少测试用例等着你。2.2 动态规划状态定义是灵魂四模的压轴题几乎每次都会落到动态规划上2019年这场也不例外。我印象中有一道题是两个序列的匹配相关具体形式类似“给定两个字符串你可以替换、插入、删除字符求最少操作次数让它们相等”——没错就是编辑距离的变体。编辑距离之类的题难点不在于知道要用DP而在于状态定义和转移方程能不能一次写对。我见过太多人的状态定义是对的但初始化写错导致答案全偏。标准的编辑距离DP是这么定义的dp[i][j]表示字符串a的前i个字符转换到字符串b的前j个字符所需的最少操作次数。初始化时dp[i][0] i表示把a的前i个字符全部删除dp[0][j] j表示从空串插入j个字符。转移时如果a[i-1] b[j-1]则dp[i][j] dp[i-1][j-1]否则取三种操作的最小值加一。写成代码就是这样def min_distance(a: str, b: str) - int: m, n len(a), len(b) dp [[0] * (n 1) for _ in range(m 1)] for i in range(m 1): dp[i][0] i for j in range(n 1): dp[0][j] j for i in range(1, m 1): for j in range(1, n 1): if a[i - 1] b[j - 1]: dp[i][j] dp[i - 1][j - 1] else: dp[i][j] min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) 1 return dp[m][n]这段代码看起来简单真正笔试时你可能会慌尤其在有时间压力的情况下。我的心得是DP题写不出来时先不要急着动代码用一个小例子在草稿纸上把dp表画出来。比如aabc、byabd把每个格子填一遍转移方程自然就清楚了。还有一个容易忽略的坑dp[i][j]的更新依赖于dp[i-1][j]、dp[i][j-1]、dp[i-1][j-1]所以遍历顺序必须是i从小到大、j从小到大。有人图省事用一维数组做空间压缩如果没处理好更新顺序结果全错。笔试时如果空间不紧张直接用二维数组最稳。2.3 排序与贪心先证明再动手排序与贪心是笔试中性价比最高的一类题——你不需要掌握复杂的数据结构关键是能看出来“这题要先排序再按某种规则贪心”。但正因为思路看起来简单很多人反而不重视证明导致用错排序规则。四模里有一道区间类题目大概意思是给定若干个会议的开始时间和结束时间问最多能参加多少个不重叠的会议。这就是经典的“会议室问题”或者“活动选择问题”。正确解法是按会议结束时间从早到晚排序然后依次选择结束最早且与已选会议不冲突的会议。def max_meetings(times): times.sort(keylambda x: x[1]) count 0 last_end 0 for start, end in times: if start last_end: count 1 last_end end return count为什么按结束时间排序而不是按开始时间这个问题的答案就是贪心算法正确性的核心。我当年上课时老师用反证法证明过在所有可行方案中如果一个方案的第一场会议不是结束时间最早的你把它替换成结束时间最早的会议后面的剩余时间只会更多不会变少。所以按结束时间排序的贪心策略一定是最优的。笔试时你不需要写出严格的数学证明但至少要能在脑中过一遍这个逻辑避免出现“感觉对了但实际是错的”的情况。我见过不少人把这道题按会议时长排序答案看起来也有道理但用例一跑就暴露了问题。贪心题最怕的就是“看起来对”的伪正确解法。2.4 双指针与哈希表时间复杂度的博弈2019年四模的数组题有一个很有意思的特点——大部分题目暴力O(n^2)都能过一部分测试用例但想拿满分必须优化到O(n)或者O(nlog n)。这背后其实模拟了真实笔试的判分逻辑小数据给部分分大数据才给满分。所以你需要掌握的不仅是能跑的解法还得是会根据数据范围选解法的能力。有一道题是“给定一个整数数组和一个目标值找出数组中两个数之和等于目标值的下标”。这是LeetCode第一题“两数之和”的原题。暴力双重循环当然能做但数据一大就超时。标准解法是用哈希表记录已经遍历过的数字及下标每遍历到一个新数字就检查“目标值减去当前数字”是否已存在于哈希表中。def two_sum(nums, target): seen {} for i, num in enumerate(nums): diff target - num if diff in seen: return [seen[diff], i] seen[num] i return []这个解法的时间复杂度从O(n^2)降到了O(n)核心思想就是用哈希表的O(1)查找换取暴力的O(n)遍历。这个思想在很多“找配对”“找目标”类题目中都通用比如三数之和、四数之和、以及一些变形的子数组问题。双指针的思路也类似。很多数组题如果数据本身有序你就可以用两个指针从两端向中间逼近省掉一层循环。比如经典的“容器盛水”“三数之和”都是双指针的典型应用。我刷四模时的一个重要心得是遇到数组题先看有没有序再看能不能用双指针实在不行再上哈希表或者排序辅助。3. 实战解析三道典型题从读题到AC全流程前面讲了考点和通用套路现在挑三道比较有代表性的题目我把从读题、构思、编码到提交的全过程拆解一遍。这个过程比单独的答案更有参考价值——因为它展示了遇到一道陌生题时脑子里应该怎么想。3.1 字符串压缩一道“简单但不送分”的题题目大意给定一个只包含字母的字符串按照“字符连续出现次数”的规则进行压缩。比如aabcccccaaa压缩后是a2b1c5a3。如果压缩后的字符串长度不小于原字符串则返回原字符串。这道题的数据范围不大不需要特殊优化线性扫描一遍即可def compress(s: str) - str: if not s: return res [] count 1 for i in range(1, len(s)): if s[i] s[i - 1]: count 1 else: res.append(s[i - 1] str(count)) count 1 res.append(s[-1] str(count)) compressed .join(res) return compressed if len(compressed) len(s) else s这道题的关键点在于“压缩后长度不小于原字符串则返回原字符串”这个附加条件。很多人算完压缩结果后直接输出了忘了这个比较逻辑。笔试中这种“规则里藏着的小条件”是最容易丢分的读题时必须用笔划出来。另一个小细节是a压不压缩按规则a压缩后就是a1长度是2大于原长度1所以应该返回原串a。这个点不测一下很容易漏但机器判题一定会测。复盘心得这类题不需要什么高级算法纯粹考细心程度。我重刷时给自己规定拿到题先写出“输出规则的三要素”再动手——条件是什么、长度怎么算、何时用原串。把这三句话写在草稿纸上后面写代码就是翻译题。3.2 最长无重复字符子串滑动窗口的经典应用这道题在四模里应该是以“寻找给定字符串中最长不含重复字符的子串长度”出现的。原题来自LeetCode第3题是滑动窗口的经典入门题。思路是这样的用两个指针left和right维护一个窗口窗口内保证没有重复字符。right不断向右扩展每次遇到一个新字符如果它已经在窗口中出现过就把left移动到该字符上次出现位置的下一个位置同时更新该字符的最新位置。def length_of_longest_substring(s: str) - int: pos {} left 0 max_len 0 for right, ch in enumerate(s): if ch in pos and pos[ch] left: left pos[ch] 1 pos[ch] right max_len max(max_len, right - left 1) return max_len这段代码看起来很简单但有几个细节值得展开。注意pos[ch] left这个判断pos字典里存的虽然是字符最近一次出现的位置但如果这个位置已经在窗口左边界之外说明当前窗口里并没有这个重复字符不需要移动left。很多人省略了这个判断结果遇到abba这种字符串就出错——right走到最后一个a时pos[a]还是0但left已经变成2了如果再直接用left pos[a] 1就会把left从2退回1窗口内就会出现重复字符。这种边界细节就是我反复强调的“模板背下来了不一定能AC”的原因。算法框架谁都会写但边界条件才是拉开差距的地方。复盘心得滑动窗口类的题目记住一个口诀右指针负责扩张左指针负责收缩窗口内容用固定规则维护。每次递归或迭代时先问自己四个问题窗口里存什么什么时候应该扩大窗口什么时候应该收缩窗口用什么数据结构存窗口内容最方便把这四个问题想明白这类题基本就不会卡壳了。3.3 跳跃游戏贪心策略的优雅之处四模压轴区有一道“给定一个非负整数数组每个元素代表你在该位置可以跳跃的最大长度判断能否从第一个位置跳到最后一个位置”的题目。这是LeetCode第55题“跳跃游戏”的变体也是一道很经典的贪心题。初见这道题的人很容易想到递归或DFS从位置0出发尝试所有跳跃步数看能不能到达终点。这种解法在小规模数据下可行但一旦数组变长分支数量呈指数级增长直接超时。正确的做法是用贪心def can_jump(nums) - bool: max_reach 0 for i, num in enumerate(nums): if i max_reach: return False max_reach max(max_reach, i num) return True这个解法的核心思想是我们不关心具体走哪条路只关心从起点出发当前能到达的最远位置是哪里。遍历过程中如果当前位置已经超出了最远可达范围说明前面没有任何一个位置能跳到这直接返回False。否则不断更新最大可达位置直到遍历结束。这个贪心策略的正确性在于所有可达位置是连续的。如果某个位置可达那么它之前的所有位置一定可达。所以只要维护一个最大可达边界不需要准确知道每条路径的细节。这里有个容易混淆的点它和动态规划的区别。如果是问“跳到终点最少需要多少步”那就需要用BFS或者贪心配合计数LeetCode第45题但本题只问“能不能到”所以用一个变量就够了。做算法题时读完题先问“求什么”是可行解问题还是最优解问题这决定了用哪种策略。复盘心得贪心题最难的地方不是写代码而是判断“这题能不能用贪心”。我做四模时的一个经验是——如果题干里出现“最多”“最少”“能否”这类字眼先想想贪心能不能做能构造出反例再考虑DP。很多同学一看到这类题就条件反射式地开DP数组其实浪费了大把时间。4. 考场实战策略从读题到提交的时间管理有一套好的解题思路还不够笔试毕竟是限时游戏时间分配和心态管理同样重要。我根据自己的笔试经验结合刷四模的感受整理了一套实操性很强的考场时间管理方案。4.1 前5分钟全局扫题建立优先级拿到题目的前5分钟不要急着写代码先快速把所有编程题都看一遍。每道题只花30秒扫一下题干和数据范围然后在草稿纸上标注两个信息预估难度和数据规模。预估难度根据你熟悉的考点来判断比如看到“字符串、模拟”标记为低难度看到“动态规划”标记为高难度数据规模用来决定要不要考虑优化比如n 10^5的数组题基本可以排除O(n^2)暴力解法。扫完这遍之后快速确定做题顺序。我的经验是先做你最有把握、代码量最小的题哪怕它不是最简单的——先拿分稳住心态。再做“需要一点思考但方向明确”的题这类题通常每题耗时10-15分钟。最后啃最难的综合题如果15分钟内没有有效思路直接跳过把时间留给检查。这个策略的核心原则是分数最大化而不是“把所有题都做完”。笔试不是考试排名AC几道题就是几道题。死磕一道题导致后面所有题都白卷是最亏的。4.2 写代码时保持“防御式编程”所谓“防御式编程”就是默认输入可能是脏的默认边界情况会发生默认系统会有陷阱。具体到牛客的ACM模式有几个习惯建议刻进肌肉记忆读取字符串后用.strip()去掉末尾换行符除非你明确知道保留空格有意义。如果题目要求处理多组数据务必用while True加try包住读取逻辑捕获EOFError退出。数组的下标越界比内存溢出更常见凡是涉及i 1、i - 1、二维数组的行列索引一律先手动模拟一遍临界值。变量命名用能看懂的名字不要用a1、a2这种毫无信息量的缩写——考场上你肯定不想在Debug时还得回忆哪个变量是干嘛的。这些习惯不需要刻意练习刷题时每次提交前都自查一遍慢慢就会变成条件反射。4.3 多个测试用例不要只看样例过了就提交我见过太多人写完全部样例通过就立刻提交结果0分。这里说的样例指的是题面给的那一两个输入输出示例。这些样例通常很简单覆盖不到边界条件。正确的做法是在本地自己多构造几个边界用例打一遍对于字符串题测试空字符串、单个字符、全部相同字符、全部不同字符。对于数组题测试数组长度为1、所有数字相同、数组倒序、最大极值输入。对于动态规划题在纸上手动跑一个n3的简单用例验证状态转移表是否正确。这些额外测试每个大约花1-2分钟但能帮你拦截掉一大半的提交错误。我最后一次刷四模前给自己立了一个规定提交前至少造4个边界用例跑一遍没有边界用例的提交统统视为“裸奔”。5. 常见问题与排查技巧实录刷这套题的时候我也遇到了不少典型问题这里挑几个有代表性的记录下来都是实战中总结出来的不是教科书上那种“理论上可能发生”的问题。5.1 本地运行正确提交后却异常这大概是牛客上最常见的求助帖标题。原因通常有这么几类第一输入输出格式问题。本地你可能直接用一个写死的字符串测试但线上输入是多行或者末尾有空格、空行。解决办法是统一使用sys.stdin.read()读取处理或者保证每次input()后都调用.strip()。四模的字符串题经常在输入末尾藏一个\r\nWindows和Linux的换行差异也会导致类似的诡异报错。第二全局变量污染。如果你用Python写多组测试用例且代码中有全局变量或默认参数很容易在多个用例之间互相影响。解决办法是尽量把逻辑封装到函数里每个测试用例调用一次函数确保局部变量不会残留。第三递归深度超过限制。如果用了DFS类解法且数据规模较大Python默认的递归深度限制约1000层可能会触发RecursionError。这通常需要改成迭代式或显式栈写法。5.2 程序超时需要优化牛客的判题系统有明确的时间限制一般是1到2秒。如果你的算法在大数据下超时优先检查这几个点是不是用了Python内置的list.index()在循环里查找元素这个操作是O(n)的循环套一遍就是O(n^2)。是不是用str做大量拼接操作Python的字符串不可变每次都会生成新对象。要改成list收集再用.join()。是不是能用双指针或哈希表把O(n^2)降为O(n log n)或O(n)四模的数组题几乎都是这个套路。我用一个实际例子说明有一道数组题我最初用的是暴力两重循环在小数据下测试通过但提交后提示超时。后来改成哈希表时间复杂度从O(n^2)降到O(n)成绩直接从超时变AC。这个案例很典型——暴力解法值20分优化算法值100分。5.3 答案错误边界条件和四舍五入答案错误和超时相比更难排查因为系统不会告诉你错在哪个用例。我的排查顺序是先检查数值范围题目给的n上限是10^9时用int没问题但如果你把中间结果存成浮点数就可能精度丢失。尤其是涉及除法、开方时能用整数运算就别用浮点。再检查边界下标动态规划数组开的是(n1)还是n遍历时range(1, n1)还是range(n)差一个单位整个表就全错了。再检查输出格式要求输出保留两位小数你是用了format(x, .2f)还是直接print(x)要求输出空格分隔你最后是不是多打印了一个空格这几个点都是我自己踩过的坑。2019四模有一道题要求输出浮点数保留两位小数我第一次直接用print(avg)结果一直报错后来改成print(f{avg:.2f})才通过。这种格式问题在本地完全看不出来只有提交时才会暴露。6. 从2019四模到未来的笔试这套题还能怎么用把题目本身讲完之后我想聊聊这套三年前的模考题在今天复习过程中的定位。很多人觉得旧题就不值得刷这其实是个误区。算法题的底层考点变化非常慢今天大厂笔试考的还是双指针、动态规划、字符串处理、贪心这些核心内容只是包装更花哨了。2019四模的价值在于题型设计干净、边界条件清晰非常适合用来做基础查漏补缺。如果你现在是准备笔试的初期阶段建议按这个顺序使用这套题第一遍不限时间每道题独立完成允许查资料但必须保证提交AC用来定位薄弱点。第二遍限时90分钟模拟真实考场环境把做题顺序和时间分配练成本能。第三遍只做错题和卡壳超过15分钟的题每次重做前先复述思路再动手写。这三次刷题法对任何题集都有效只是四模这套题的体量和难度特别适合作为中间的检验工具。它不像新手题那么平淡也不像压轴难题那么劝退。说到Python如果你用Python刷题这里顺便提一句很多人关心Python在OJ上性能不够的问题。我的体会是对于绝大多数笔试编程题Python的PyPy解释器性能完全够用关键在于不要写出低效的代码——比如在循环里做字符串拼接、高频调用print、无谓的深拷贝。数据结构选对了Python一样能AC绝大多数题目。最近的Python一级编程题考试题目也延续了这个趋势重点考的依然是基础语法、字符串处理和简单算法逻辑和这套四模题集的底层能力要求高度重合。所以如果你想从更基础的语法题开始热手先练Python入门级别的编程题再上牛客模考这类综合题衔接是比较顺畅的。7. 刷题之外的三个效率建议题目本身讲了这么多最后再分享几个关于刷题效率的通用建议这些不一定只针对四模但对所有编程题复习都有效。7.1 错题本比刷题量更重要我见过太多人刷了几百道题但遇到原题还是不会做原因就是没有整理错题本。整理错题不需要很复杂建立一张表格记录三列就好题目名称、错误原因、正确思路一句话。比如“两数之和——没用到哈希表暴力超时——用哈希表存遍历过的数O(n)查互补数”。每次刷题前翻一遍错题本比重复刷一百道简单题高效得多。四模这套题我刷了三遍每遍用的错题本记录都不太一样。第一遍记的是“这个知识点没学过”第二遍记的是“这个边界条件没想到”第三遍记的是“这道题5分钟内能否AC”。你会发现随着复习深入错题本的内容会从知识点转向策略这本身就是水平提升的信号。7.2 写完代码后花2分钟口述思路这个方法听着有点玄学但我实测非常有效。每道题AC之后用手机录音或者对着文档口述一遍你的解题思路包括为什么选这个算法、状态怎么定义、边界怎么处理、复杂度是多少。口述的过程会迫使你把模糊的想法清晰化如果发现自己说不清楚那说明你并没有真正理解这道题只是碰巧调通了。口述还有一个额外的作用面试前的模拟。技术面试经常让你现场讲题如果平时没有口述习惯面试时容易语无伦次。把口述当成刷题的固定步骤之后面试讲题会自然很多。7.3 每周一次限时自测找考场感觉刷题和考试是两种不同的能力。有些人平时刷题很猛一到考试就紧张原因是对“限时”没有概念。我的建议是每周选一套完整的模考题不一定是四模任何成套的题都可以按照真实笔试的时长和规则进行自测。限时自测时注意三点关闭IDE的自动补全、不用搜索引擎、不中途暂停。这三个条件模拟的是考场上的真实约束。自测完不管分数怎么样对照错题本分析一次找出时间都花在哪了然后针对性地调整做题策略。比如你发现自己总在前两道简单题上浪费时间纠结“最优解”那后面做题就要允许自己先用暴力解法拿分AC了再想优化。写在最后的个人体会把2019年牛客四模这套题完整重刷一遍之后我的感受挺深的。三年前的题目今天看起来依然很扎实——它没有为了难而难每一道题都踩在基础算法和工程习惯的交汇点上这恰恰是笔试最看重的东西。我当年第一次刷这套题的时候大概只AC了两道很多现在一眼能看出套路的题目当时连门都摸不着。现在能比较轻松地解出来靠的不是什么天赋就是日复一日地刷题、记错题、复盘思路。如果你现在正处于刷题瓶颈期觉得怎么练都提不高我的建议是别急着刷更多新题回头把做过的旧题尤其是模考题重新做一遍。你会发现很多当时“AC靠运气”的题现在能说清楚每一步为什么这么写也能发现一些当时“AC了就过了”的题其实还隐藏着更优的解法或者没考虑到的边界。这个过程比刷十道新题都更锻炼人。最后再分享一个小技巧做题的时候把每道题当作一次“教导别人”的机会写完代码后假装有人问你“为什么这里要加这个条件”然后自己回答。这个简单的问答练习能帮你把很多模棱两可的理解补扎实也是我从刷这套四模题里收获最大的一件事。