不少刷题的同学都有同感:面对“某高校在线评测系统”(下文统一用 S校OJ 代称)里几百道题,最头疼的不是题目本身,而是不知道怎么安排刷题顺序。很多人从第1题开始按题号硬刷,刷到一半发现前面的题目偏难、知识点跳跃,后面又反复遇到重复题型,效率非常低。我最近把 S校OJ 的题目按“知识点 + 难度梯度 + 专题应用”重新做了一份新版题目顺序目录,把零散的题号变成一条可执行的刷题路线,用下来顺手很多。这篇文章就把我做这份目录的思路、完整结构和使用方法分享出来,也顺便聊聊维护目录时踩过的坑,希望能帮你少走弯路。
1. 旧版目录的痛点与新版的改进思路
1.1 旧版目录为什么不好用
最早S校OJ的题库是直接按提交时间排版的,打开题目列表,第1题可能是一道字符串基础题,第2题突然跳成高精度计算,第3题又变成图论模板题。这种排列方式对管理员来说最省事,对做题的人却很不友好。我见过不少同学从第一题开始刷,刷到“高精度加法”时还没系统学过数组和字符串,只能先去查资料,刷题节奏彻底被打乱。
旧版如果做过分类,一般也只是按AC率排序,或者简单分成“入门”“提高”两个文件夹。但AC率高不等于适合新手,有些题AC率高纯粹是因为有大量提交试错,比如一道逻辑判断题,大家反复提交几次总能过,但它涉及的位运算知识一点都不基础。此外,旧版目录很少标注题目之间的先后依赖关系,比如“并查集”题目明明应该放在“图的最小生成树”前面,但目录里两者中间隔了十几道动态规划题,完全看不出关联。
1.2 新版目录的设计目标
我这次做新版目录,核心目标只有一个:让一个只学过C语言基本语法的人,能按顺序刷下来,逐步进阶到可以完成S校OJ上大部分中等难度题目。为了实现这个目标,我定了四个设计原则。
一是按知识点组织,而不是按题号组织。每道题都归属到一个明确的知识点标签下,比如“循环结构”“一维数组”“二分查找”“最短路”。这样刷题时你知道自己到底在练什么,而不是盲目刷。
二是难度要有梯度,而且要平滑。每个专题下的题目都按“模板题 -> 变形题 -> 综合题”排列,模板题用于理解算法核心流程,变形题用于巩固细节,综合题用于联系其他知识点。如果某专题内没有足够多的题,就允许从其他OJ补充相似题,但主体仍是S校OJ原题。
三是建立跨题号索引。因为在平台上题号是固定的,所以新版目录要额外提供一份“题号 -> 专题位置”的对照表。这样就算你已经刷了一半旧题,也能根据对照表快速定位自己现在应该跳到哪个专题继续,不用从头开始。
四是标注重复题与过时题。S校OJ有一些题目是早期学期遗留的,题目描述不清、数据范围不合理,甚至存在多题本质相同的情况。新版目录里我会明确标注“建议跳过”或“优先做其中一道”,避免重复劳动。
1.3 为什么顺序比数量更重要
有人会觉得,刷题数量够了不就行了,顺序有那么重要吗?我一开始也这么想,后来辅导朋友刷题时发现,顺序不对会导致两个问题。第一个问题是知识断层,比如说先做“树状数组”再做“线段树”,你会觉得线段树很抽象,因为你没法从树状数组的二分思想自然过渡到线段树的区间分解;反过来,先做“线段树”的基础题,再回头写树状数组,你会豁然开朗。第二个问题是挫败感太强,在还没掌握递归时直接遇到“深度优先搜索”题,卡了两个小时做不出来,很容易让人怀疑自己是不是不适合编程,其实只是顺序没排对。
所以顺序目录本质上不是“把题目排个序”那么简单,它是一张完整的学习路线图,里面包含了知识点之间的依赖关系、难度提升的节奏、以及复习巩固的时间点。这也是我坚持做新版的原因。
2. 新版题目顺序目录的完整结构拆解
2.1 一级分类:从语法到综合的五层结构
我把S校OJ的题目分成了五个大阶段,每个阶段对应一个刷题周期。
第一阶段是“基础语法与输入输出”。包含顺序结构、分支结构、循环结构、数组、字符串、函数、结构体、指针这些内容。这一阶段的目标不是做出难题,而是确保能熟练把一个数学问题转换成代码,并且能稳定地通过OZ的评测。注意这里不是只做题,我建议每题都要用至少两种写法完成,比如循环题既写for也写while,这样后面学算法时思路更活。
第二阶段是“基础数据结构与STL”。覆盖栈、队列、链表、哈希表、堆、树、图的基本遍历。在这一阶段,你会大量使用C语言的数组模拟以及C++的STL容器。很多同学会跳过容器直接手写数据结构,但我建议先学STL再看实现原理,因为实践能帮你理解抽象接口到底解决了什么问题。
第三阶段是“进阶算法与经典方法”。也就是二分、双指针、贪心、动态规划、搜索回溯、分治、并查集、图论最短路与最小生成树、数论基础。这一阶段是大头,题目最多,我按照“算法思维 -> 模板代码 -> 综合应用”的顺序细分了十几个子专题。
第四阶段是“数学思维与字符串高级算法”。包括组合数学、概率期望、数论扩展、FFT、字符串哈希、KMP、AC自动机、后缀数组。这一阶段适合已经能稳定解出中等难度题的同学,用来挑战S校OJ里的压轴题和竞赛题。
第五阶段是“综合实战与模拟赛”。这个阶段不再按知识点分,而是把每周完成题数作为目标,练习把多个算法组合起来解决一个大问题。我还会从往年的课程设计题里抽取几道综合性强的放到最后。
每个阶段的划分不是拍脑袋拍的,我参考了S校OJ的题目来源。它的题目主要集中在基础课程和程序设计竞赛训练,前四个阶段刚好覆盖了这些知识点。而且我对比过同一套算法在不同题上的AC率分布,发现按这个顺序刷,中间过程的AC率能稳定在60%以上,不会因为知识缺失导致完全卡死。
2.2 专题内的微排序:模板题、变形题、综合题
每个子专题下,题目顺序我不是随手排的,而是按“模板题 -> 变形题 -> 综合题”的三级递进。
模板题的特点是题意清晰、解法固定、边界条件少,通常讲完算法就能直接写出AC代码。比如在“二分查找”专题里,我先放一道“查找有序数组中某个数第一次出现的位置”的题,这就是模板题。它只考察二分的基本写法,不会掺杂“浮点数二分”“二分答案”等变形。
变形题会在模板题基础上增加一些障碍,比如要求处理重复元素、要求修改判定条件、或者把数组变成二维的。比如同一专题里的“旋转数组的最小数字”,它虽然也是一个有序数组但旋转过,你需要自己判断在哪半边缩小范围。这类题的意义是让你熟练掌握二分的“边界控制”。
综合题则是把二分和贪心、二分和前缀和、二分和DFS结合起来。比如“在最长连续子序列长度至少为K的限制下,最大化最小值”这种题,你不仅要识别出可以二分,还要能写出正确的check函数。综合题通常放在专题末尾,因为你只有先把模板题和变形题做完,才能快速定位需要用二分去解决哪个子问题。
我把这种微排序落实到目录中的方式是用表格记录每题:题号、题目名、所属子专题、难度标签、前置知识、推荐顺序。这样目录不只是个列表,还能指导你“在做这道题前先看哪些题”。
2.3 配套的题号映射表与更新机制
新版目录最重要的配套文件是一份“题号映射表”。因为S校OJ的题号是稳定的,但我的顺序目录里不会用题号作为主要标识,而是用“阶段-专题-序号”。比如“3-07-04”表示第三阶段第七个专题的第四题。映射表左边是阶段序号,右边是实际题号,列表按阶段序号排序。
这样设计有个好处:如果OJ新增了题目,我只需要在对应专题末尾插入新题,后面的题序号自动顺延,原来的映射不会大规模错乱。比起直接用题号排序,这种逻辑编号更稳定。
更新机制上,我建议每个月维护一次映射表。因为S校OJ偶尔会下架一批重复题,或者修正题目描述和测试数据,如果不定期更新,目录中的超级链接会失效。我会在映射表中加一个“最近更新日期”字段,并在每月初自动检查一次。检查的方法是搜索OJ题目列表,对比前100道新提交题是否在映射表中存在,如果存在就说明题目更新了,需要及时补录。
我还做过一个脚本,用Python解析S校OJ的HTML题目列表,自动提取题号、标题、AC数和提交数,然后和我的映射表比对。比对逻辑很简单:先找是否有题号丢失,再找是否有题目标题变化,最后根据AC率变化调整难度标签。虽然OJ的页面结构偶尔改版,但脚本用的选择器只需要改两三个类名就能继续跑,整体维护成本并不高。
3. 如何使用这份目录高效刷题
3.1 根据目标倒推刷题路径
光有目录还不够,得知道自己想通过刷题得到什么。我见过三类目标不同的使用者,他们对应的刷题路径差别很大。
第一类是课程驱动的同学,目的是通过学校编程课考试。这类同学不用按照完整五个阶段刷,只做前三个阶段的基础专题就可以了。具体操作是:打开目录,第一阶段每天完成8到10题,第二阶段每天完成4到5题,第三阶段每天完成2到3题,优先做“模板题”和“变形题”,综合题跳过或选做。这样四周左右就能覆盖课程知识,期末上手做编程大题会稳很多。
第二类是竞赛驱动的同学,目标是ACM或程序设计竞赛拿奖。这类同学可以直接从第三阶段开始,但第一阶段和第二阶段不能完全跳过,需要每天抽半小时快速复习。重点放在“进阶算法与经典方法”里的动态规划、搜索、图论和数据结构的综合题上。第五阶段模拟赛模块要严格执行,每周至少完整做一套,且要限定在3小时内,训练临场节奏。
第三类是求职面试驱动的读者,虽然很多面试不直接考OJ,但算法题是绕不开的。这类同学时间紧,不用每个专题都做透,按照“必备高频专题”清单来走:数组、链表、栈队列、哈希、二分、双指针、贪心、动态规划、DFS/BFS。目录里每个这样的专题,我只建议做前80%的题目,后面的综合题如果涉及竞赛知识也可以直接跳过。
为什么目标要先明确?因为刷题最忌讳的就是“平均用力”。我见过一位同学准备校招,把S校OJ的数论进阶题刷了二十多道,但连最常见的二分答案都写不利索,最后面试考了一道二分变种直接挂掉。目录里的顺序是通用最优路线,聪明人应该把它改造成最适合自己目标的路。
3.2 配合OJ的评测记录建立个人刷题日志
目录只是静态路线,真正帮你进步的是动态的刷题日志。我强烈建议不要只依赖OJ自带的提交记录,因为那些记录只有“AC/WA/RTE”,看不出你对一道题的真实掌握程度。我在使用新版目录时,会额外维护一张表格,列是“日期、题号、专题、难度、首次AC耗时、错误次数、复盘笔记”。
复盘笔记是最关键的一列。我通常记录三件事:第一,刚开始看到题目时有没有思路,还是直接看了题解;第二,WA的原因是什么,是边界没考虑全、算法选错,还是代码实现有问题;第三,下次遇到同类题需要提醒自己什么。很多题刷完就忘了,但如果你认真填了这列,过一个月回来看,复习效率会高很多。
另外我会对每道题打一个“掌握度”标签:S(能独立做出来,且能在5分钟内说清思路)、A(需要一点提示但能独立写出来)、B(看了题解才能做出来)、C(至今没搞懂)。目录里推荐的下一题,我通常只把已完成的S和A题作为依据。如果某个专题里B和C题太多,就说明我的前置知识没打牢,要暂停推进、回头补前置专题,而不是硬着头皮继续刷。这个机制可以防止“刷了题但没学懂”的假努力。
3.3 目录中重复题和超难题的取舍
S校OJ里重复题比想象中多。同一个算法,可能出三四道变体,甚至连题目描述都差不多。我在目录中会用“同质题”标签把这类题归并,只推荐你完整做完第一道,后面的同质题直接取AC记录证明你已经掌握即可,不用再看题面。
还有一种情况是超难题,通常是竞赛题里的压轴,放到普通专题里会显得难度特别高。目录里我会用星号标出“选做”。我建议初学者直接跳过,因为把时间花在能力范围边界附近15%的题目里效率最高。如果一道题你看了两遍题解仍然不能独立复现,那它目前就不适合你,一年后再回来做可能十分钟就AC了。强行啃难题带来的进步远没有系统巩固基础带来的大。
让我具体举一个例子:S校OJ的“动态规划”专题,我列出了40题,其中基础线是前25题,进阶线是第26到35题,最后5题标了选做。基础线要求每道题都能做到S级掌握;进阶线允许你看题解,但要能独立复现;选做题则完全看兴趣。这样做的好处是,不同水平的人拿到同一份目录,都能找到自己的舒适区边缘。
4. 制作或维护这类目录的实操心得与避坑指南
4.1 信息收集与校验:避免题号错位和题目改名
制作目录最核心的工作其实是信息收集,而不是排序。S校OJ的题目列表通常是动态加载的,直接复制页面只能拿到当前页的内容,而且不少题目有多个别名,比如“A+B问题”和“a+b problem”其实是同一道题,搜索结果会让人误判。我从头到尾踩了不少坑,总结出几条经验。
第一,获取题目清单时,不要靠手动翻页复制。最好写一个简单的请求脚本,带上正确的Cookie和User-Agent,逐页抓取题库页面,解析出题号、标题、AC数、提交数。抓取时注意控制请求频率,别给服务器造成压力。S校OJ是个老系统,频繁请求很容易触发限制,导致IP被封一段时间。
第二,抓下来的数据一定要去重。你可能会发现同一道题出现在“基础题”和“历次作业”两个分类下,但题号不同,其实题目内容一样。我会用标题的归一化字符串做第一次对比,再抽样看几道题的内容,确认是否真的是重复题。判断重复的标准是:核心输入输出相同、算法模型相同、只有取值范围或者叙述上的小改动。
第三,校验题号与标题的对应关系。旧题在多次改版后,部分题号可能被重新分配,或者原来的链接变成了跳转页。我的办法是批量访问“/problem/题号”页,检查HTTP状态码以及页面中的标题标签是否和抓取到的标题一致。如果有一批链接返回404,就说明题号列表过期了,需要根据OJ的最新题库重新生成映射。
第四,注意测试更新造成的难度漂移。一道题以前的AC率是30%,某天管理员改了数据范围或加了冷门用例,AC率可能掉到15%。这时目录里标记的难度也应当同步调整。我会在映射表中额外记录“上次AC率”和“当前AC率”,如果差异超过10个百分点,就人工检查一下题目变更记录,重新评定难度。
这些操作看起来繁琐,但很值得。因为一份题目顺序目录最大的价值是“准确”,一旦题号错位,读者跟着错题刷,浪费时间不说,还会产生自我怀疑。我宁可在维护时多花半小时校验,也不愿让目录里的错误迷惑他人。
4.2 用表格还是在线文档:我的选择与理由
我最早是用一个巨大的Excel表格来维护目录,列有阶段、专题、题号、标题、难度、AC率、前置知识等。后来发现一个问题:Excel虽然排序方便,但多人协作时很麻烦,而且手机上看不方便。于是我换成了在线协作文档,分Sheet维护,效果好了很多。
不过在线文档也有缺点:目录越来越长之后,搜索时会自动加载一大堆内容,卡顿严重。为了解决这个问题,我是按“阶段”分页签存放的,每个Sheet只承载一个阶段的题目列表。第一阶段大概100题,第二阶段80题,第三阶段150题,第四阶段60题,第五阶段40题,每个Sheet都在几百行内,加载很流畅。
我给每一列都设置了明确的数据格式:题号必须是整数,难度用“入门、简单、中等、较难、困难”五级枚举,前置知识用“专题编号 + 短横线 + 可选知识点”格式,比如“3-06-并查集”。数据格式统一后,判断“初二段位是否合适”就非常方便,你可以用筛选功能直接把前置知识里没掌握的题过滤掉。
还有一种更轻量的方式,就是直接维护一个Markdown文件,传到自己的代码仓库里。这个方案适合单机使用,可以用脚本在本地解析Markdown表格,生成待刷题列表。不过如果你没有代码基础,我建议还是用在线文档,因为它内置筛选和排序功能,对小白友好。我目前的习惯是:在线文档做日常使用,Markdown文件做发布用的“只读版”,两者从同一份数据源生成,保证内容一致。
4.3 维护频率与版本更新策略
目录不是一次性产品,需要长期维护。根据我的经验,维护频率可以分三个级别。
每日维护主要是刷题状态的更新,比如你完成了某道题,把它标记为“已AC”,或者发现某道题已经失效,顺手提个issue。每日维护不要做大规模排序调整,否则会频繁变目录顺序,导致他人正在刷的进度对不上。
每周维护是重排“推荐顺序”的时机。因为随着题库更新,每周可能会多出几道新题,这些新题未必都是新算法,可能只是旧题换个描述,我会把它们归到对应专题,并摘掉重复题标签。每周维护还要检查一次“最新提交榜”,看看最近大家都在做哪些题,这类题往往是课程作业的重点,要在目录中提高优先级。
每月维护是做一次全面体检:重新跑一遍爬虫,比较所有题目的标题、AC率和提交数是否有变化,再把失效链接和题号映射错误修正,最后发布一个新的版本号。版本号我习惯按“yy.mm”记录,比如“25.04版”,这样读者能知道自己拿到的目录是否过期。
我维护这套目录将近一年,最大的体会是:目录的价值不在“排得多好看”,而在“长期准确”。很多人想做一份完美的做题计划,结果花了三周做出来,然后放在收藏夹吃灰。其实更合理的做法是先用一个月做出80分的目录,边刷边改,通过持续小更新把它慢慢进化到95分。我在第一周的目录还很粗糙,很多专题顺序不合理,但是刷题的进度没有停,等刷完第三章再回来调整,思路就清晰多了。
5. 常见问题排查与使用建议
5.1 题号对应不上怎么办
这是使用目录时最容易碰到的问题。常见原因有三种:一是OJ改版后题目ID被重置;二是管理员清理了部分旧题目,导致链接404;三是网站出现短时间故障,访问题目页时跳到了登录页。
解决步骤也很简单:先在OJ首页的搜索框输入题目原名,看有没有结果;如果搜不到,就翻看一下原题目的提交按钮是否还在,部分被隐藏的题目可以直接通过提交页访问;实在不行,就在目录的映射表里找到这题的“逻辑编号”,然后查看同专题的其他题目。同专题内的题目往往难度相似,跳过一题对整体刷题影响不大。
这里有一个重要的提醒:不要在题号对应不上时自己去改目录里的所有编号。因为一道题的映射错位很可能是临时的系统问题,你大范围改完之后却发现原有题号又恢复正常,反而会导致双份错乱。我一般是在映射表中把这道题的状态改为“待检查”,等48小时后再确认一次,才决定是否删除或重新映射。
5.2 题目难度和个人感受不一致怎么办
目录中标注的难度是根据AC率和我个人做题感受综合评定的,肯定有主观因素。如果你觉得一道“中等”题怎么都做不出来,先不要怀疑自己。可以先检查前置知识是否满足:比如“字符串哈希”前置要求是“哈希思想”和“字符串常规操作”,如果你前两类没做扎实,做起来当然难。
还有一种情况是AC率误导。有些题AC率看着高,是因为历史提交有个大牛用暴力踩过去了,数据比较弱;而AC率低也可能只是因为题目描述不清,导致大家都在瞎试。这时候我建议你把这道题从“必做”降到“选做”,不要因为它卡住影响整体进度。等刷完同专题其他题之后,再回头做,往往会轻松很多。
还有个经验是,难度感受和题目的表述风格关系很大。S校OJ里有一批老题,题目很长,故事背景复杂,但解法很简单;另一批新题题面简洁,解法却非常绕。前者适合放在入门阶段,后者更适合在熟悉算法后再做。我在目录中会根据“题面复杂度”和“算法复杂度”双重标注难度,如果某题只是故事长但算法简单,会特别标注“题面刺客,算法简单”。
5.3 如何判断一道题是否优先做
在有限时间里,最怕的就是花大量时间做了一道“非优先”题。我给目录中的题打上了三个优先级标签:P0、P1、P2。
P0题是核心基础题,每道都值得独立完成并复盘。这类题的特点是覆盖了某个算法最本质的流程,比如最短路里的Dijkstra模板,动态规划里的01背包,搜索里的DFS模板。刷P0题的时候,如果你能一次性AC,基本说明这个算法你已经入门了。
P1题是重要变形题,用来加深对算法的理解。P1题允许你参考题解,但参考前要先独立尝试15分钟。如果15分钟没有思路,再看题解,然后把这道题标记为“需要二刷”,一周后回来重做。P1题做完后,你应当能画出算法的流程草稿,能用嘴说明白“为什么这样设计状态”。
P2题是拓展题或重复题,有时间就做,没时间可以直接跳过。尤其是在面试和考试前,不要碰P2题,因为边际收益太低。每次准备刷题前,我会先列出这一周的P0和P1清单,完成它们之后再考虑P2清单。
我在实际刷题中还有一种“三遍法”来应对难题:第一遍最多花半小时尝试,做不出来就看题解,理解后自己写一遍AC代码;第二遍在第二天做,要求不参考题解直接AC;第三遍在一周后做,要求同时能给别人讲清楚思路。只有通过了三遍法,这道题才算真正掌握。这个方法大概率会被低估,但效果显著。
5.4 刷题卡住时的心态调整与降级策略
刷题卡住是常态,不是异常。我遇到过连续两天被一道并查集优化题卡住,后来才发现是因为我没理解“路径压缩”和“按秩合并”各自的适用场景。这种时候,硬刚的时间成本很高,正确做法是降级。
降级策略分三步:第一步,把当前题目标记为“暂缓”,返回目录中该专题的前置题目,把前面所有P0题重新AC一遍,强化基础;第二步,找一个更简单的同类题作为过渡,如果卡在“线段树的区间更新”,就先做“不带头懒标记的单点更新线段树”,等把它弄明白再回来;第三步,如果还是卡,就干脆跳到下一个专题,过两天再回来。大脑在无意识中会持续处理之前的问题,短暂放松往往能帮助你跳出来。
另外我还建议,做题时间不要超过CPU时间限制太多。比如OJ时间限制是1秒,你写了个暴力算法跑了3秒,这时候不要反复在OJ上提交碰运气,而应该主动分析复杂度,估计是否超时。用目录中的前置知识去判断是否需要换更优算法,也是这个道理。
5.5 从目录到自建的刷题知识库
当你使用这份目录刷到一定阶段后,可以开始维护自己的知识库。我的做法是,每完成一个专题,就写一篇短文总结该专题的常见套路和易错点,并把对应题号链接放在文章底部。比如写完“二分”专题总结后,我整理了四类套路:在单调序列上二分、二分答案判断可行性、二分浮点数、二维二分。每类套路都附上S校OJ原题,这样以后复习时不需要重新翻题库。
知识库建立起来之后,目录本身就不再只是“做题顺序表”,它变成了一棵知识树的索引。你可以从一个专题的题目列表,直接跳转到你的笔记;也可以从笔记反向找到所有相关题目。到这一步,你就真正实现了用题目带动学习,而不是被题目牵着走。
我个人在实际操作中还有一个习惯:每个月月底,把这份目录连同我自己的刷题日志一起导出成PDF,作为阶段总结保存下来。半年后回看,你能清楚看到自己在哪里卡得最久、在哪里进步最快。这种可视化的成长轨迹,比任何刷了多少题的统计都有说服力。新版目录的维护并不复杂,难的是持续去做。希望这篇文章能给你提供一个靠谱的起点,让你在S校OJ的题海里游得更从容。