刚接触科技行业前两年时,我一直把“刷题”当成一种学生时代遗留的自律游戏,直到后来去面试、带新人、搞代码评审,才真正意识到 OJ 这套东西没有过时,反而是所有软件能力验证里最“皮实”的一环。不管是华为 OJ、东华 OJ,还是现在团队里常用的 LeetCode 风格题单,底层逻辑都是一样的:你用代码去解决一个被明确定义的问题,机器立刻给你打出分数。这种事前残酷、事后诚实的形式,放在日常工作里其实很难找到替代品。
这篇文章不谈“OJ 是什么”这种教科书定义,我想从实际使用的角度出发,把 OJ 刷题这件看似普通的事拆开来看。你会看到不同平台的真实差异、选题练手的节奏安排、从读题到 AC 的整体流程,以及我最想输出的那部分——那些机器永远不会告诉你的边界条件和隐藏经验。无论是即将开始备战秋招的在校生,还是想用 OJ 帮团队搭考核题的负责人,这篇文章应该能给你一些可以直接落地的参考。
1. 内容整体设计与思路拆解
1.1 OJ 解决的核心问题到底是什么
很多初学者会把 OJ 简单理解成“判题工具”,其实这个认识低估了它。从工程角度看,OJ 系统本质上是一套完整的验证闭环:它对题目描述做形式化约束,对输入输出做边界限定,再用测试用例去校验你的代码行为。这意味着它不只考你会不会写代码,还考你能否准确理解需求、能否预判所有可能的输入情况、能否写出资源占用合理且不带隐含副作用的程序。
我在参与公司内部搭建考核题平台时,对这种机制体会更深。一个设计良好的题目,拿到真实场景里对应的是低级的实现任务:把一笔交易记录标准化、把一个协议字段解析出来、把一批日志按规则聚合。你以为 OJ 只是在练流程控制,换个角度看,它其实在模拟需求方会怎么刁难你——极端输入、超大整数、空集合、只有一个元素的数据。这些恰恰是实际开发里最容易出 bug 但又最不容易被测试覆盖的角落。
所以聊 OJ 题之前,先在心里定个调子:刷题不只是“为了面试”,更是为了训练自己思考边界条件的习惯。带着这个视角再去刷,你会发现自己不在死磕一道题的通过率,而是真正关心“为什么这个错误样例我之前没想到”。
1.2 OJ 平台的选型逻辑:不是越难越好
提到华为 OJ、东华 OJ、POJ、洛谷、Codeforces、LeetCode,很多新手容易陷入“到底哪个平台更权威”的争论。我在不同阶段都用过这些平台,坦白说,没有绝对的优劣,关键看你当前的目的是什么。
| 平台类型 | 典型代表 | 适合阶段 | 特点 |
|---|---|---|---|
| 国内大厂题库 | 华为 OJ、东华 OJ | 准备机试、校招入门 | 题型贴近国内企业,场景化强,边界刁钻 |
| 竞赛平台 | Codeforces、洛谷 | 想系统性提升算法 | 周赛氛围浓,解法多样,数据强度大 |
| 面试题库 | LeetCode、牛客 | 求职冲刺 | 以面试高频题为主,讨论区丰富,解法模板多 |
| 经典老牌题库 | POJ、HDU | 计算机专业基础训练 | 题量大,经典题多,但评测环境较老 |
如果你是在校生,刚开始准备学校的 OJ 作业,我会建议以东华 OJ 这类校内平台为主。缺点是论坛氛围冷清,优点是对接老师的教学进度,题目的考点范围很明确,适合按知识点查漏补缺。
如果是面向大厂求职,华为 OJ 值得专门留出时间熟悉。它的题目特征和普通竞赛题有区别:题面会更像一个工程描述,而不是纯粹的算法游戏,很多题带有明显的状态机味道和输入干扰项。这一点挺关键的,因为在真正机试的时候,读懂题目条件里的潜台词往往比写出正确算法更决定你能不能过。
1.3 方案选型的核心原则:用什么策略刷
我见过很多人打开 OJ 之后就是“榜单上哪个通过率低我刷哪个”,结果刷了三个月,感觉每天都很努力,真正遇到新题依然没有思路。这就是典型的选型策略错误。
有效的练题路径,应该按专题递进而不是按难度随机。按照两年的带队经验,比较稳妥的路线是先建立基础语法和常用容器的肌肉记忆(数组、字符串、哈希表、栈、队列),再进入排序、二分查找、双指针、滑动窗口这类高频套路,然后才轮到 DFS/BFS、动态规划、贪心这些需要建模能力的内容。
一个重要的逻辑是,大部分 OJ 题不是考察你有没有“天赋”,而是考察你对有限题型的熟悉程度。题目表层千变万化,剥到底层就那几类模型。你在一个专题上连续积累 20 道题以后,会发现新题基本都是在旧模型上加了一层伪装。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析与实操要点
2.1 读题时最容易忽略的三个信息点
读题这件事看起来没有技术含量,其实决定了你后面所有步骤的走向。我自己吃过最大的亏,就是不重视数据范围说明。一个数组题如果说明 n <= 10^5,那么 O(n) 甚至 O(n log n) 都可行;如果要暴力 O(n^2),基本必超时。数据范围不是填空题里装饰性的数字,它直接给你圈定了算法的复杂度天花板。
第二个容易忽略的是输出格式。不少题目对输出有严格约定,例如每个结果占一行、行尾不能有多余空格、结果要求保留几位小数。这类错误在 OJ 里被判成 Presentation Error,很多人以为算法错了,其实只是输出没匹配。实际机试中在这种地方反复出错,非常影响心态,所以读题时就要把输出格式当作需求条款来圈注。
第三个信息点在多组输入数据的题目里特别常见:题目可能要求读到文件尾,也可能要求先读入测试组数。前者需要 while(cin >> n) 或者 while (scanf(...) == 1),后者则需要先确定循环次数。我以前带过的新人里有好几个在这上面翻车,原因是从小到大习惯把输入想成一锤子买卖,没有建立起“流程持续进行”的意识。
2.2 输入输出到底怎么写才算正确
输入输出是 OJ 刷题的基本功,但也是很容易被轻视的细节。很多本地 IDE 跑得好好的代码,一提交到 OJ 就报错,问题往往出在输入输出格式和缓冲区处理上。
先说输入。如果题目要求先给一个整数 n,然后下一行给 n 个数,最常见的写法是:
cpp复制int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
这里有个小坑,有些题型会给出多行不定长的输入,比如每行一个测试用例,直到文件结束。这种情况下如果不处理 EOF,你的程序会在本地自动“等输入”,提交到评测端就会在规定时间内没有输出而被判超时。正确的写法是:
cpp复制string line;
while (getline(cin, line)) {
// 处理每一行数据
}
再说输出。很多初学者喜欢在循环体里 cout << ans << endl;,这在小数据量题里没问题,但如果循环次数达到十万次以上,频繁刷新输出缓冲会明显拖慢程序。更稳妥的做法是改用 '\n' 或者在结束时统一输出。别小看这种微小差异,某些强数据 OJ 上,IO 效率确实可能就是 TLE 和 AC 的分界线。
在多数 Java 提交场景中也存在类似的点:System.out.println 如果调用十万次,会有一定性能损耗。如果你使用 Java 刷题,建议将输出内容先收集到 StringBuilder 中,最后统一输出,这算是最简单的 IO 优化经验。
2.3 边界条件与极端数据的处理技巧
很多人卡题,不是卡在核心算法,而是卡在边界条件的处理上。这类问题在真实工程中也极其常见,所以 OJ 才反复考你。
拿最简单的“查找数组中的最大值”来说,第一反应是:
cpp复制int maxVal = a[0];
for (int i = 1; i < n; i++) {
if (a[i] > maxVal) maxVal = a[i];
}
但如果题目允许数组长度为 0 或者没有明确保证 n >= 1,这段代码先访问 a[0] 就已经越界了。处理这类问题,最稳妥的习惯是在设计算法之前先问自己三个问题:输入为空怎么办?输入只有一个元素怎么办?输入的数据全相同怎么办?把这三种情况在思路里过一遍,基本能覆盖大部分边界隐患。
再比如二分查找的边界处理,写 while (left < right) 还是 while (left <= right),中间值用 mid = (left + right) / 2 还是 mid = left + (right - left) / 2,看似都是小事,但前者在处理大整数时可能溢出,后者在某些条件下会陷入死循环。我在实际答疑中经常和大家强调,不要靠背模板去写二分,而要把区间不变量的逻辑理清楚。你拿几组对称的测试数据去推演一遍,比死记写法有效得多。
2.4 内存与运行时间的平衡实操
搞定边界后,还有一个约束容易被忽视,那就是资源限制。OJ 平台通常会在题目说明中给出时间限制和内存限制,比如“时间限制:1 秒,内存限制:256 MB”。你设计的算法,不仅要逻辑正确,还要在这两个硬指标下跑完。
初学者最容易担心的就是“这里的递归层数会不会爆栈?”以及“我这里开了一个二维数组会不会超过内存?”我的经验是,先看数据范围再做预估。比如一个二维 DP 题需要 n * m 的状态表,如果 n = m = 1000,那就是 100 万个状态,每个状态如果是 int 类型占 4 字节,总共约 4 MB,完全没问题。但如果 n = m = 10000,那就是 100 万个状态乘以 100 倍,变成 400 MB,明显超限,必须考虑状态压缩。
提交代码前先计算空间复杂度,是一项非常好用的习惯。我在机试前给自己定的标准操作是:写完代码先目测一遍复杂度,不急着交;估算通过后,再自己造几组边界数据跑一遍,确认输出没问题,然后再点提交。这套流程虽然有点“强迫症”,但在关键场合确实帮我挡掉过很多次无效提交。
3. 实操过程与核心环节实现
3.1 从拿到一道 OJ 题到 AC 的完整步骤拆解
我自己刷题从来不直接跳到代码编辑器,而是会走一套固定的解题流程。这套流程简化下来只有四步:读题画重点、手动模拟样例、抽象数据结构与算法、写代码并自测。听起来有点老生常谈,但很多人在第一步就跳太快了。
第一步,读题的时候手里拿一支笔(或者用键盘高亮),把三种信息划出来:输入格式、输出格式、数据范围。这三个信息决定你的策略选择。剩下的题面描述,看完之后最好能在心里复述成一句话:这是个“在有序数组里找到第一个大于某个值的数的位置”,还是个“给定一些区间,求合并后区间的长度”?如果一句话说不清,说明你还没真正读懂题。
第二步,手动模拟样例。不要上来就在脑子里跑算法,用笔画一遍样例输入到样例输出的过程,体会中间每一步发生了什么转换。这一步能避免许多因理解偏差导致的“白写”。举个工作中常见的情况,有人在“字符串反转”这道题里把整个字符串反转了,但题目其实要求反转每个单词的顺序但单词内部顺序不变。不手动模拟样例,很容易完全跑偏。
第三步,根据数据范围确定算法。数据范围是筛选算法的核心依据,如果 n 只有 100,那暴力没问题;如果 n 到 10^5,那你基本上要往排序、双指针、二分、哈希这类复杂度接近 O(n log n) 的方向去想。这个问题想清楚了,代码结构基本就浮出水面了。
第四步才是写代码,但写完不急着提交。先在本地把题目的样例输进去跑一遍,再把几个边界数据塞进去验证。比如数组只含一个元素、输入为空、数值达到最大边界,这些数据在样例里往往看不到,但恰恰是评测系统最爱藏的坑。
3.2 代码模板与实际提交演示:以 C++ 为例
为了更容易理解,我拿 C++ 最常见的基础输入输出模板做一个实际演示。特别是公司机试或竞赛场景,你最好在开始答题前就把输入输出模板敲熟,而不要现场回忆。
cpp复制#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
while (cin >> n) {
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
// 在这里按具体题目逻辑编码
long long sum = 0;
for (int x : a) sum += x;
cout << sum << '\n';
}
return 0;
}
上面这段代码能覆盖很大一类“多组输入,每组先给 n 再给 n 个数”的场景。ios::sync_with_stdio(false) 和 cin.tie(nullptr) 这两行是 C++ 刷题里常用的 IO 提速设置,能明显减少 scanf/printf 与 cin/cout 同步带来的性能损耗。虽然日常工程中不推荐混用两种风格的 IO,但 OJ 环境下这种写法非常实用。
用 Python 刷题的同学也有对应的写法。多行数据读取时,不建议逐行 input() 然后切分,性能会偏慢。常用的做法是一次性读取:
python复制import sys
def solve():
data = sys.stdin.read().strip().split()
if not data:
return
# 按次序解析 data 中的每个 token
it = iter(data)
n = int(next(it))
arr = [int(next(it)) for _ in range(n)]
# 处理逻辑 ...
if __name__ == "__main__":
solve()
这种写法在面对大输入量时比逐个 input() 稳定不少,不过要留意内存占用。如果输入达到几十 MB,一次性读取倒还没问题;如果是几百 MB 级别的极端大输入,逐行读取可能更好。
3.3 实战选例:一道典型 OJ 题的推演过程
直接用一道很经典但又不至于太难的题目来推演一遍:给定一个无序整数数组,找到其中第 k 大的元素。
第一步看数据范围:如果数组长度达到上万级别,那么最直接的排序方案 sort(nums.begin(), nums.end(), greater<int>()) 并且直接取 nums[k-1] 是可接受的,时间复杂度 O(n log n)。如果长度达到千万级别,排序可能不再是稳的,此时基于快速选择(Quick Select)的思路可以把平均复杂度降到 O(n)。
第二步自测,尤其是处理 k 的取值。很多人容易直接把 k 理解成数组下标,忘记了题目里说“第 k 大”通常是从 1 开始计数。如果你要返回排序后的第 k 个元素,下标必须 k-1。这类问题如果不跑边界数据,直接在样例上是看不出来的,因为样例很可能只给了常规取值。
第三步提交。如果一开始误用了 O(n log n) 的排序,而数据被卡到极限,你可能看到 TLE;如果你直接用 Quick Select,但 partition 逻辑写错,可能出现死循环或错误答案。定位这些信息,就需要回到数据范围和代码细节上一层层排查。
3.4 如何在 OJ 平台内提高测试覆盖率
OJ 的评测结果只有二进制判定:通过或不通过。你在本地测过了样例,并不代表能 AC。这里有一个小技巧:尽可能自己构造随机测试数据,然后用暴力解法跑一遍生成基准答案。
比如你在做一道需要优化的题目时,可以先写一个非常慢但逻辑一定正确的版本,例如 O(n^2) 暴力枚举,然后生成若干组小规模随机数据,比较你的高效解法和暴力解法输出是否一致。如果不一致,说明高效算法里存在细节上的逻辑漏洞,这时再回头查代码定位问题。这个方法在竞赛圈里叫“对拍”,我每学期带新人时都会让他们尽早掌握。
对拍的思路很简单:写一个随机数据生成器。比如要测数组排序题,就随机生成一个长度在 10 到 20 的数组,元素范围也随机定。然后把你的答案程序和暴力程序跑同一份输入,逐行比较输出。只要规模够小,暴力程序一定能在合理时间跑完。不同输出出现的那一次,就意味着你找到了一个让算法露馅的角落。
3.5 平台相关配置与本地环境差异
不同 OJ 平台的后台编译环境不一致,会给提交带来一些意想不到的麻烦。比如有些老牌 OJ 默认用 C++98 标准,你本地用 C++17 写 auto、结构化绑定、unordered_map 初始化列表都能编译通过,但放到 OJ 上可能会直接编译失败。
处理方式是提交前留意题面或平台说明里标注的编译环境。如果平台默认标准较老,写代码时尽量使用更基础的语法。华为 OJ、东华 OJ 这类面向求职或教学场景的平台,通常会对编译环境有明确说明,建议提前找到对应帮助文档或者查看往届考生讨论帖,锁定版本要求。
另外还有一个小差别是行尾符:Windows 环境下换行是 \r\n,Linux 环境下是 \n。如果你手写文件解析逻辑,行尾符可能会影响结果,考虑兼容性时最好统一 trim 掉行尾空白。好在大部分 OJ 会忽略输出行尾的空格细节,但“多一个空格”这类问题在某些严格题目上可能被判定为格式错误,自己本地造数据时尽量以题面输出要求为准来避免争议。
4. 常见问题与排查技巧实录
4.1 常见错误类型速查与对应思路
刷 OJ 的时候,评测结果里的英文缩写很容易让新人一头雾水。这些状态并不是随机的,每个缩写都对应一类非常具体的失败原因,掌握它们的含义能让你排查问题时快很多。
| 判定结果 | 含义 | 常见原因 | 排查思路 |
|---|---|---|---|
| AC | 通过 | 无 | 无 |
| WA | 答案错误 | 算法逻辑漏洞、边界条件没覆盖 | 构造边界数据,使用对拍 |
| TLE | 超时 | 算法复杂度过高、IO 过慢、死循环 | 检查复杂度是否匹配数据范围,检查循环退出条件 |
| MLE | 超出内存限制 | 数组开得过大、递归栈过深 | 计算内存占用,考虑滚动数组或迭代 |
| RE | 运行时错误 | 数组越界、空指针、除零、递归栈溢出 | 检查下标访问、检查递归深度 |
| PE | 格式错误 | 输出格式与要求不符 | 检查空格、换行、标点与题面要求是否完全一致 |
WA 里有一类特别隐蔽,就是“按题目要求你的答案是正确的,但返回的格式差一点”——比如该用 double 输出却转成了 int,或者题目要求保留两位小数却直接输出原数值。这种问题看样例往往看不出来,最好重新逐字读一下输出要求。
TLE 也很常见,但并非所有 TLE 都意味着你的算法不够好。如果出现死循环,也会被评测系统用超时拦下来。一个典型的死循环场景是在二分查找里用了 mid = (left + right) / 2,然后更新区间时写错成 left = mid 或 right = mid 而没有加一减一,当区间长度为 2 时就会永远在同一个状态里打转。
RE 最常见的原因是数组越界。很多人喜欢把数组开成 n + 1 以防万一,这个习惯不错,但如果你使用的是从 0 开始的下标,开 n + 1 也不代表你访问 a[n] 就安全。做题前先确定下标语义,是“闭区间”还是“开区间”,把所有用到下标的地方统一逻辑,这样能减少很大一部分越界问题。
4.2 本地能跑,提交就错?可能的原因分析
“本地运行正常、提交后 WA 或 RE”是 OJ 新手问得最多的问题。这类问题的原因并不复杂,往往是本地测试数据太弱,或者本地环境与 OJ 环境存在差异。
最常见的原因就是测试数据不足。很多人在本地只拿题目样例跑一遍,样例通过了就觉得没问题,但题目样例通常只是“示意数据”,不会穷尽边界条件。真正的提交环境里,评测系统会塞入大量边界值和极端值。处理方法就是前面提到的对拍,以及多造几组更极端的数据,比如 n=1、所有数字相同、数值接近题面上限等。
还有一个原因是变量初始化。有些情况下本地编译器会把未初始化的变量默认处理为 0,但换到另一套环境下,这个值可能是随机的。如果你的代码逻辑错误地依赖了“默认值为 0”,在本地测试时表面跑得对,提交后就可能翻车。所以每次声明局部变量时都主动赋初始值,是极其重要的安全习惯。
另外,全局数组定义后会自动初始化为 0,而局部数组不会。如果你误以为局部数组也会清零,很容易导致计算结果多出一些不可预期的影响。新手排查问题时如果百思不得其解,不妨回头扫一眼有没有这类初始化遗漏。
4.3 基于实际经验总结的五条避坑技巧
第一条,谨慎使用递归。某些树的题目天然适合递归,但递归深度如果过大,会导致程序爆栈。如果题目数据范围显示树的高度可能达到 10^5 以上,建议改写为显式栈的迭代方式。很多 OJ 的 C++ 默认栈空间并不大,你无法保证测评机一定会放宽限制。
第二条,乘法和加法留意溢出。比如计算数组总和时,如果你错误地用了 int 接收,而数据总和可能超过 2^31 - 1,结果就会出现异常。看到数据范围很大时,优先想到 long long。记住,你在做题里用的不是工程上“追求最小内存”的写代码习惯,而应该以稳为主。
第三条,注意全局变量和局部变量的命名冲突。如果一个变量既在全局声明又在局部声明,局部使用时会遮蔽全局变量,在复杂题目里容易带来隐蔽的错误。刻意养成给全局数组加前缀的习惯(比如 g_nums)能省下不少排查时间。
第四条,别忘记重置数据结构。多组输入数据时,如果在循环外声明了容器,循环内没有清空,就会把上一组数据带到下一组,导致输出异常。每次处理完一组数据后,养成手动 clear() 的习惯。
第五条,调试输出记得删。本地调试时往代码里加了一堆 cout 或 console.log,提交时没删干净,轻则影响性能,重则让评测系统判定格式错误或 WA。最好在提交前全局搜一下调试打印相关的关键词。
4.4 刷题过程中的心态与节奏问题
最后聊一个不算技术但很影响效果的东西:刷题的节奏感与情绪管理。接触 OJ 时间长了你会发现,成绩好坏有时不完全是能力问题,更像是“在有限时间里怎么分配精力”的问题。如果你卡在一道题超过 40 分钟还没有任何思路,我建议直接标记收藏,暂时跳过。因为硬耗会挤压后面题目的时间,而且很容易造成过度纠结,越写越乱。
把时间线拉长,我会建议固定的专题交替策略。比如连续一周每天刷两道“双指针”类型的题目,下一周换成“二分搜索”,再下一周进入“动态规划”。每进入一个新专题,前三天是痛苦的,因为理解模型需要铺垫;但扛过一周之后,你会发现新题型的识别速度明显提高。
给团队搭考核题时,我也会设置合理的题目梯度。头两道是基础语法题,对应“能写代码”的底线;中间两道考容器选择与简单算法,对应“会组织数据”的能力;最后一道是动态规划或用例设计综合题,对应“在压力下解复杂问题”的水平。这种结构参考了华为 OJ 等企业级评测的设计思路,能在比较短的时间内拉开不同候选人的差距。
5. OJ 之外:平台差异、训练扩展与能力沉淀
5.1 华为 OJ 与东华 OJ 的特色拆解
既然热搜词里同时提到了华为 OJ 和东华 OJ,我以亲身体验的角度聊聊它们的差别,也给不同诉求的人一个更清晰的参考坐标。
华为 OJ 全称应该叫华为在线判题系统,通常用于华为招聘机试或者内部技能认证。它最鲜明的特点是题面描述偏工业风,会故意在题干中加入很多“包装级”细节。比如一道考字符串处理的题,题干可能是一段日志解析或命令解析,让你先从语义里剥出真正的输入输出规则。这种设计思路比较贴近真实业务,因为实际开发中需求文档本身就是信息冗余的,快速筛选关键字段是重要能力。
东华 OJ 则更多是教学场景下的答题系统,题面往往更直接,算法考点也更贴近数据结构与算法课程的章节划分。对于在校生来说,这是很好的“按章节巩固语法与数据结构”的练习场。它的评测速度通常较快,题目的输入数据规模也不会像竞赛平台那样用极限数据去卡人,新手体验会更友好一些。
我见过一些学生一上来就迷信“大厂题库才能证明实力”,忽略了校内 OJ 的系统性。实际上对我个人而言,东华 OJ 这类平台反而适合建立完整的知识树——每道题对应教材里的某个点,做起来没什么挫败感,而且能很清楚地知道“我这周学了图,那就去图论题库里刷十道”。
5.2 从 OJ 题目到工程能力的迁移路径
如果刷题只是停留在“用代码击败评测机”的层面,那确实只是一场数字游戏。真正让 OJ 刷题产生复利价值的,是你愿意把在做题中沉淀下来的思考方式迁移到工程实践中。
在代码评审中我看到很多同事在处理需求时,第一反应是“把能跑通的功能写出来”,而很少主动思考输入数据可能长什么样。这里就是 OJ 训练能补上的地方——它会强制你假设输入是十万条日志、零个用户、满内存的交易流水、异常中断的中间状态,并让你的程序在每一个假设面前都能体面地给出结果。
还有一点是输出稳定性的认知。你在 OJ 里反复锤炼过的“输出格式规范”,映射到工程里其实就是 API 返回结构约定。返回什么字段、字段名叫什么、缺失时给什么默认值,这种细颗粒度的契约感,练多了之后你会下意识地在写接口文档时比别人多想一层。
5.3 长期刷题的四个阶段划分
如果按时间跨度来规划,你会发现刷题这件事其实是分阶段的,而且每个阶段要练习的能力并不一样。
第一阶段是“从 0 到 1”的语法期。这个阶段的重点不是刷题量,而是建立“题目描述 → 代码语法 → 运行结果”的反射路径。很多人一上来就啃难题,却连 vector 排序的第三个参数怎么写都要现场查,这是效率最低的打开方式。这个阶段建议选平台上通过率较高的简单题,一次刷到连续 20 道不看题解也能独立 AC,再考虑进阶。
第二阶段是“题型识别期”。此时你已经掌握基础语法,看到题目不应该再去挨个尝试代码思路,而是先在脑内把问题归类。排序?哈希?前缀和?这种分类能力的建立需要按专题形成批量印象。我记得自己刚开始接触滑动窗口题型时,前几道题完全找不到套路;等到连续两周刷了 30 道相关题后,再看到“子数组”“连续”“窗口”等关键词,脑子里就会自动浮现维护区间状态的做法。
第三阶段是“复杂度意识期”。这时的你不仅要能 AC,还要知道自己 AC 时用的算法在最坏情况下耗时多少、空间多少。如果需要优化,往哪个方向努力。这个阶段比较适合开始刷华为 OJ 和 Codeforces 的 harder 题,因为它们的数据构造强度足够高,能真正检验你复杂度估算是否准确。
第四阶段属于“实战输出期”。这时候你可以尝试参加周赛或者特定平台的模拟机试,在有限时间内分配精力。从这一阶段开始,刷题的作用就从“练基础”变成了“做体检”,每次成绩出来你都能发现自己的薄弱环节,然后定向加强。
5.4 设定一个可执行的初始刷题方案
说了这么多理论,如果读者只想要一个可以直接开始的方案,我建议这样设定前两周的节奏,这一套也是我带新人时常用的起点:
- 第 1 到 3 天:每天 3 道“数组 / 字符串”简单题。目的不是练算法,而是熟悉提交流程和判题机制。
- 第 4 到 7 天:每天 2 道“哈希表 / 集合”应用题,比如去重、计数、两数之和这类模型。开始尝试自己分析复杂度。
- 第 8 到 10 天:每天 2 道“双指针 / 滑动窗口”中等题。这类题能帮助你建立处理连续区间问题的套路感,同时也是面试高频题。
- 第 11 到 14 天:每天 1 道“二分查找”中等题 + 1 道“排序”中等题。重点练习边界条件的推导,而不是背模板。
这套方案的最大特征是不贪多,但要求每道题都经过“读题 → 模拟 → 编码 → 自查 → 提交 → 复盘”的完整闭环。如果你觉得某一天状态不好,宁可少做一道,也不要凑数量。因为 OJ 题目的价值,不在于你做没做够 100 道,而在于你能不能从每一道题里持续提炼出新的判断力。这种判断力会慢慢长在你身上,最终变成面对未知代码问题时那种“我觉得这里不对,但我能说出为什么不对”的直觉。
我身边有些非科班转码的新人,总担心自己算法底子不够,想着先把 OJ 刷到几千题再投简历。我的看法是,不要用题量给自己设限。每周固定稳扎稳打完成一二十道,持续两三个月,能力的提升会比想象中更明显。因为支撑你面试表现的并不是某几道题目的答案,而是你面对陌生问题时的拆解路径——而这个路径,恰恰是 OJ 每天在帮你训练的东西。
