ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

东华复试OJ二刷复盘:从低级失误到稳过策略

东华复试OJ二刷复盘:从低级失误到稳过策略 距离东华复试还有十来天的时候我的OJ二刷已经推进到了第六轮复盘。说实话二刷这件事一上来是真的枯燥——同一道题明明一刷时已经AC过再打开编辑器却经常发现“根本想不起来当时怎么过的”。但熬过第四轮之后我开始感觉到了变化很多题不再依赖脑内回放代码而是能直接从题意推导出解法边界条件也处理得更自然。这篇复盘记录了本轮训练里我认为最值得反复看的几类错误、三道代表性题目的完整推导以及冲刺期的做题策略。如果你正在准备东华或者其他高校的复试OJ处于“一刷刚结束、二刷找不到重点”的状态这篇文章里记录的排查思路和刷题顺序应该能帮你省掉不少瞎折腾的时间。1. 东华复试OJ的“题感”二刷到底在抓什么1.1 复试机试和校招笔试真不是一回事很多同学准备复试机试时会直接照搬校招笔试那一套狂刷算法题、背各种套路模板。但复试OJ其实更接近“限时编程基本功测试”它不是要你发明新算法而是看你在有限时间内能不能把基础算法写对、写干净、写稳。东华复试OJ的题目量一般不大难度通常也低于ACM但限时压力很真实有时候一两个低级失误就能让你和录取线擦肩而过。二刷和刷新题最大的区别也在这里一刷的重点是“学会”二刷的重点是“稳过”。同一个题型一刷时AC了只能说明你当时的思路是对的二刷时能快速写出AC代码并且经得起特殊数据测试才算真正掌握了。所以我在二刷阶段不再追求题量的堆积而是把大量时间花在“拆解错误”和“重做旧题”上。1.2 高频题型优先级我如何重排刷题顺序二刷开始前我先把东华复试常见题型按优先级做了个排序避免把时间花在低概率考点上。下面是我整理的一份参考表不同年份、不同学校题目风格可能略有差异但总体思路是通用的。优先级题型理由建议投入高输入输出、数组/字符串模拟、排序、二分、BFS/DFS、简单DP复试几乎必考覆盖大多数中等题每天固定训练中链表操作、栈/队列、图的最短路、最小生成树、拓扑排序东华这类学校复试有概率出属于常见第二题/附加题隔天练1-2道低网络流、计算几何、FFT、平衡树复试机试基本不会考直接放弃一刷时我花了很多时间在“中”优先级题型上导致基础模拟题手速不够。二刷时我明显把重心调了回来每天前半小时做输入输出和字符串模拟再花一小时做搜索和DP最后的碎片时间才碰图论。这样调整之后做题稳定性和手速都提升得很明显。1.3 我给自己的“二刷及格线”二刷不能漫无目的地“再做一遍”我给自己定了三条量化标准简单题模拟、输入输出、排序5分钟内读题并动笔10分钟内AC。中等题搜索、简单DP、图论基础15分钟内形成思路30分钟内写出可提交代码。任何一道题如果卡了20分钟没有进展立刻标记为“待复盘”不硬耗。这个及格线看起来很功利但复试机试就是一场限时赛。平时的训练如果总是纵容自己“再想一会儿”到了考场上就会变成“再耗一会儿”。二刷的价值就在于把做题节奏练成肌肉记忆而不是真的去挑战高难度题。2. 这次二刷最扎心的三类低级失误2.1 边界值不是想不到是想到了又忘了这次复盘中我又一次在一道矩阵旋转题上栽了跟头。一刷时我就犯过错把m和n写反。按理说这种错误已经记录过了但二刷重写的时候注意力全放在旋转公式上结果输入部分读取还是先n后m导致测试数据一出现行数不等于列数的情况就数组越界。边界值处理最可靠的办法不是“做题时想一想”而是在读完输入之后立刻处理。比如矩阵题在读取完m, n后马上检查是否m 0 || n 0并且把行、列变量名统一成rows、cols不要用单个字母换来换去。还有一个很实用的习惯如果题目描述里给了数据范围就在代码注释里写上“n1”和“n最大值”这两个极端样例。等到代码写完准备提交前先跑这两个用例。这一步能过滤掉很多隐藏的越界和死循环。2.2 输入读取差一个循环带来的灾难复试OJ的输入格式通常有三种单组数据第一行给测试组数T的多组数据以及读到文件末尾EOF为止的循环输入。二刷时我一度看到while(cin n)就直接写结果有一道题其实是先给T再用T控制循环次数最后程序完全跑偏。更常见的问题是混合使用cin和getline。比如题目先读一个整数n然后读n行字符串如果直接cin n; getline(cin, str);第一行读到的往往是空字符串因为换行符还残留在输入流里。我现在的做法是如果题目出现字符串含空格的输入就统一用getline并且在读整数后手动加一个getline吞掉换行。处理多组输入时我给自己总结了一句口诀先确定循环条件再确定每组数据的初始化位置。把变量定义尽量放在循环内部避免上一组数据残留影响下一组。这个细节虽然简单但二刷时依然救了我好几次。2.3 输出格式评测机只认精确字符串这道坎我踩过很多次这次二刷又踩了一脚。题目要求输出的每个数字之间用单个空格分隔行末不能有多余空格。很多同学包括我之前的习惯会写for (int i 0; i n; i) { cout a[i] ; }这样完全AC只能靠运气因为行末会多一个空格。严格OJ会直接判WA。我现在改成用变量控制分隔符for (int i 0; i n; i) { if (i) cout ; cout a[i]; }输出格式里的大小写、Case #x:前缀、末尾换行、多组数据之间是否需要空行这些都要在读题时圈出来。我建议先把这些细节记录在草稿纸上而不是靠脑记。因为考场上越紧张越容易把平台要求的格式输出错。2.4 变量初始化静态全局数组的“安全感”假象有相当一部分同学喜欢把数组开到全局认为这样默认初始化为0比较“安全”。这个习惯本身没问题但如果一个程序里有多组测试数据而组与组之间没有重置数组就会出现“上一组数据污染下一组结果”的经典错误。二刷时有一道统计字符串中字母出现次数的题我全局定义了一个cnt[26]统计完一组输出后忘了memset导致第二组数据的结果会把第一次的计数叠加进去。排查了很久才发现第一组数据恰好让某些字母为0结果看起来像是对的可数据一换就错。解决思路很简单给多组数据的题目写代码时循环体内使用的计数数组、状态数组、标记数组一律在每组循环开头初始化。可以就用fill或memset明确重置过总比指望“默认0”更可靠。3. 三道代表性题目的推导与代码取舍二刷时遇到的题目不见得都是原题但题型可以归纳成几类。这里选三道我本轮印象最深的经典题型完整走一遍推导过程。3.1 最长上升子序列从O(n²)到O(nlogn)题目描述通常很直接给定长度为n的数组求最长严格上升子序列的长度。一刷时很多同学会先写O(n²)的DP这没错但复试的数据范围如果到10⁵O(n²)就会超时。O(n²)的思路是设dp[i]表示以第i个元素结尾的最长上升子序列长度转移时需要枚举ji。而O(nlogn)的做法维护一个tail数组tail[k]表示长度为k1的上升子序列中末尾元素的最小值。遍历每个数时用lower_bound找到第一个不小于它的位置替换掉该位置的末尾值。#include iostream #include vector #include algorithm using namespace std; int main() { int n; cin n; vectorint a(n); for (int i 0; i n; i) cin a[i]; vectorint tail; for (int x : a) { auto it lower_bound(tail.begin(), tail.end(), x); if (it tail.end()) tail.push_back(x); else *it x; } cout tail.size() endl; return 0; }这里有一个特别容易错的点题目要求“严格上升”所以要找lower_bound如果要求“非递减”需要改用upper_bound保证相等元素也能接在末尾。二刷时我差点在这个细节上翻车因为一刷时写的是非严格版本这次没仔细读题。复试现场建议先用5秒判断数据范围如果n 5000O(n²)的DP也可以快速写完保底如果n明显更大直接上O(nlogn)版本。两者都需要掌握因为不能保证现场运气一直好。3.2 二叉树的层次遍历队列写法的两个关键点二叉树层次遍历本身不难但复试常把它包装成“根据先序序列建树再输出层次遍历结果”。也就是说核心考点有两个一是建树二是层序遍历。一个常见的先序序列输入会以#表示空节点。建树用递归struct Node { char val; Node *left, *right; Node(char v) : val(v), left(nullptr), right(nullptr) {} }; Node* buildTree(const string s, int idx) { if (idx s.size() || s[idx] #) return nullptr; Node* root new Node(s[idx]); idx; root-left buildTree(s, idx); idx; root-right buildTree(s, idx); return root; }层次遍历用队列#include queue void levelOrder(Node* root) { if (!root) return; queueNode* q; q.push(root); bool first true; while (!q.empty()) { Node* cur q.front(); q.pop(); if (!first) cout ; first false; cout cur-val; if (cur-left) q.push(cur-left); if (cur-right) q.push(cur-right); } }这里最需要注意的是字符串下标的推进。建树函数中idx的位置必须严格控制每次递归处理完当前节点后要让下标指向右子树的起始位置。如果idx没有正确更新要么死循环要么建出来的树完全错乱。我在二刷时就因为少写了一个idx导致样例输出和预期完全不同调试了十几分钟才发现原因是空节点被连续跳过。复试现场遇到树题我优先选队列版本因为迭代写法不容易爆栈而且逻辑更贴近层次遍历的定义。如果你对指针操作不熟也可以用数组模拟二叉树节点但思路完全相同。3.3 拓扑排序判环入度表维护的细节有一类高频图论题是这样的给定n个任务和m组依赖关系问能否按照依赖顺序完成所有任务。这本质上就是判断有向图是否存在拓扑序列或者说是否含环。Kahn算法的核心是维护每个节点的入度把所有入度为0的节点放入队列逐个弹出后将邻接点的入度减1再次把入度变为0的节点入队。如果最终出队节点数量等于n说明存在拓扑序否则有环。#include iostream #include vector #include queue #define MAXN 1005 using namespace std; vectorint edge[MAXN]; int indeg[MAXN]; bool topo(int n) { queueint q; for (int i 1; i n; i) { if (indeg[i] 0) q.push(i); } int cnt 0; while (!q.empty()) { int u q.front(); q.pop(); cnt; for (int v : edge[u]) { indeg[v]--; if (indeg[v] 0) q.push(v); } } return cnt n; }上面这段代码本身很简单但二刷时我在“重边”上踩了坑。如果输入里有两条相同的依赖边a - b有些同学在构造图时可能默认去重但如果没有去重indeg[b]会被加了两次导致b永远无法入队。更稳妥的做法是先读完全部边再用set或标记数组去掉重复边再统计入度。另外edge和indeg在多组数据时必须在每组开头重置。我习惯把这两个数组定义在topo内部或者每次调用前统一clear。这个点看似是代码洁癖但在考场上能直接决定你是否能一次AC。复试中图论题出现频率不算特别高但拓扑排序是“既基础又容易包装成实际问题”的考点二刷阶段值得花一晚上把Kahn算法写熟并搞清楚它和DFS判断环的区别。4. 一刷和二刷的差异错题本应该怎么用4.1 我的错题本结构一刷时我习惯把所有WA的题解都截图保存结果复盘时连自己错在哪都找不到了。二刷我改成结构化错题本每道错题只记四栏日期题型/题目特征错误类型一句话总结第6轮矩阵旋转行列搞混统一用rows/cols变量名读入后先查边界第6轮字母计数组间数据污染多组数据循环内显式重置计数数组第6轮拓扑排序重边导致入度错误构图前去重或读边时同步去重这里关键的是最后一栏“一句话总结”。能用一个短句写明白的错误原因说明你真的理解了如果只能写“代码WA、检查后发现数组越界”这种模糊总结那下次大概率还会犯。4.2 二刷时如何判断自己“真会了”我判断一道题是否需要“三刷”标准很简单能不能在不看任何代码的情况下给一个完全没做过这道题的同学讲清楚思路。如果讲的时候还需要翻题解说明这题还没变成自己的东西。复试机试和期末考试不一样它不允许你临时翻笔记。所以二刷目标就是让一道题变成“本能反应”。每次重做旧题时我会先逼自己在10分钟内手写一遍核心代码然后再对照之前的错题总结。如果两次写法一致且AC就在错题本上划掉这道题如果有新的理解就追加一句新的总结。4.3 时间分配和心态不要连续三天只刷同一类题二刷阶段我在心态上走过一段弯路连续三个晚上只做动态规划做到第四天看到“状态转移”四个字就生理性恶心做题正确率也明显下降。后来我把训练内容打散每天规定“1道输入输出/模拟 1道搜索或DP 1道图论或树”有时候还会穿插字符串处理。这样每种题型都能保持手温又不会让大脑在同一个思维模式里疲劳。坚持几天后整体手感比集中刷一类题要稳得多。4.4 复盘到了第六篇我开始做减法前五篇复盘我写得很细甚至会把某段代码一行一行贴出来。到了第六篇我发现真正值得写进复盘的反而只有“错误类型”和“效率瓶颈”。刷题数量上去后每天真正全新的失误点其实不会超过三个大量WA是旧错误的变体。所以本篇复盘也在刻意做减法不再堆砌所有做过的题只记录三类典型失误和三道代表性题目。如果你的二刷已经进入中后期我也建议你把错题本里的“水分”挤掉只留下那些能在冲刺期快速提醒自己的核心要点。5. 复试前的冲刺安排把二刷成果转化成考场手感5.1 最后三天的全真模拟二刷到了这个阶段不应该再漫无目的地刷题。我会在正式复试前三天每天安排一次完整的机试模拟。选一套题型和难度都比较接近复试的模拟题设定和真实考试一样的时长然后关闭所有笔记和题解像真正考试一样从头做到尾。模拟时要注意两个细节第一不要中途停下去查资料哪怕卡住了也要按考场规则来第二记录每个题的第一遍提交结果而不是“反正模拟嘛可以多交几次”。模拟的目的就是暴露真实水平多交几次只会掩盖问题。5.2 考场上的时间止损顺序复试机试时间一般比较紧我先读完全部题目再按以下顺序执行题面最短、输入输出最简单的题力争5分钟内AC这是拿分基本盘。自己最擅长的算法题比如熟悉的DP或搜索稳定拿分。剩余题目按“思路清晰分值高”来排序啃得动就写啃不动就及时收手绝不恋战。这个顺序的核心思路是先把确定的分拿满再用剩余时间冲击难题。很多同学喜欢一上来就啃第一题最难的那道结果第一题卡半小时后面送分题没时间写这是复试机试最可惜的失败方式。5.3 东华OJ平台操作的小细节最后一天我还会做一件小事把所有参加复试会用到的代码模板重新敲一遍。比如快读模板、建树模板、拓扑排序模板不复制不粘贴手动输出一遍确保考场上不会因为忘记头文件这种理由浪费心理资源。提交语言选对也很关键。东华OJ支持C的话我保险起见会用标准头文件iostream、vector、algorithm等而不是bits/stdc.h因为不同OJ对万能头文件的支持不一样。另外main函数一定是int类型并且返回0这是最基本的AC条件。还有一点容易被忽略很多OJ对输出最后一个空格的处理很严格对换行的要求却没那么统一。有的题目说“每组数据输出后跟一个空行”有的说“每组数据间用一个空行隔开”。读题时看到这类词在草稿纸上画一个输出效果示例能有效避免理解偏差。整个二刷过程让我最受益的其实不是AC数量变多了而是面对WA的时候不再慌。以前一看到Wrong Answer就怀疑自己算法不行现在会先检查输入输出格式再检查边界条件最后才回头看核心逻辑——这个排查顺序本身就帮我省掉了大量无用功。复试前的最后几天比起追难题我更建议你把过去复盘中所有“低级错误”快速过一遍。这些内容不会被写进高分经验帖但恰恰是考场上最不值、却最可惜的扣分点。二刷的意义说到底就一句话把“会做”变成“稳做”把“碰运气”变成“确定性”。希望这篇复盘能让你少走一点弯路也希望大家都能稳稳上岸。
返回列表