从OJ三道题复盘到企业机考:多组输入、边界控制与调试避坑指南
2月26日晚上我把OJ上编号104、105、106的三道题全部刷完顺手截了个图发到宿舍群配字今日任务完成。室友回了一句你天天刷这玩意儿到底图啥我当时随口说“练手呗”但睡前认真想了想这个问题其实值得好好回答。在在线编程与OJ评测系统上刷题表面上是拿AC、刷通过数底层练的是读题拆解、边界控制、代码习惯和调试能力。这些东西在期末笔试里未必考但在企业机考、面试手撕代码、甚至日常写业务代码时全都会回流到你身上。这篇就借2.26刷的三道题按“评测机制—题解复盘—调试实录—避坑清单”的顺序讲透算是一份给同样在刷OJ的新朋友的参考。适合刚接触OJ的算法新手、正在准备华为OD等企业机考的求职者也适合想带学生刷题的信息技术老师。题号104、105、106在不同学校OJ上对应的题面可能不同但这三道题涉及的核心知识点非常经典多组输入、数组边界、字符串读取。我把这些通用套路拆开讲清楚你回头刷自己学校的OJ也能照着同样的思路去分析。1. 一次普通刷题记录为什么要单独写一篇1.1 我为什么每天都在和OJ打交道我现在的日常是每天晚上固定留半小时到一小时打OJ。很多人觉得OJ就是“做题”但真正坚持下来的人会告诉你它其实是一种低成本、高反馈的代码训练方式。你提交一段代码评测系统立刻告诉你对不对对了就AC错了就WA、TLE、RE……这种即时反馈比很多课程作业一拖一周强太多了。尤其在三、四月这个节点很多同学会同时准备校招和一些技术类岗位的在线测评。像华为OD这类岗位简历过了之后第一关就是OJ机考题目风格和高校OJ非常像但更偏应用场景。我身边也有人一开始就在牛客、力扣上刷但说实话基础不牢的时候先在高校OJ把输入输出、循环、数组、字符串这些问题磨熟再去做那些偏思维、偏场景的题节奏会稳很多。回到2.26这天。我打开的是学校OJ的题目列表104、105、106是三个连续编号。当时选这三道是因为它们刚好覆盖了入门阶段最重要的三类能力正确处理多组输入、掌握数组的边界控制、搞定字符串的读取和处理。三道题都不算难但每一道都值得认真复盘因为它们的“坑”极其典型。1.2 这篇内容适合谁能解决什么问题先说结论这篇适合三类人。第一类是刚开始用OJ刷题的新手你可能连AC和WA都还没有完全搞懂这篇会从评测机制讲起让你明白代码提交后到底发生了什么。第二类是刷了一两周但经常卡题的半新手你已经能AC一些简单题但遇到104、105、106这种“看似简单却总差点意思”的题时容易反复WA这篇会把最常见的坑和调试方法列出来。第三类是马上要参加企业机考或需要带训学生的读者可以把里面的避坑表和自检清单当成备课或复习材料。关于题号需要先说明一句不同学校的OJ104、105、106对应的题面可能完全不一样像有些学校用的是自己搭的在线评测系统有些学校直接挂在开源OJ框架上题号规则五花八门。我这篇以自己2.26晚上刷到的一版为准重点提炼的是题型和通用套路。你如果在自己学校的OJ上看到不同题面完全可以照着同样的思路去分析核心知识点是通用的。这也是这篇内容想传达的一个观念不要纠结于某一道题本身而是把每一道题当成一类问题的代表。2. 先搞懂OJ的评判逻辑再谈刷题2.1 OJ怎么判你的代码从提交到出分很多新手一上来就闷头写代码提交之后看到WA就懵了其实很浪费时间。先花十分钟搞清楚OJ到底怎么评价你的代码后面能少走很多弯路。OJ的基本流程是评测系统准备好若干组输入数据然后用你的程序依次跑这些数据把你的程序输出和标准答案逐字符比对。要注意这个比对是精确到空格和换行的这就是为什么有些人逻辑完全正确却因为最后多打了一个空格被判WA。评测还会记录每个测试点的运行时间和内存超过题目给出的时间上限或内存上限就会分别判TLE和MLE。理解这套流程以后你就能明白“本地能跑过”不代表“提交能过”。本地只有一个样例样例过了只能说明你的程序对这一个输入是正常的OJ上的数据是隐藏在后台的一组数据覆盖了普通情况、最小情况、最大情况、极端边界情况任何一个点出错都会被抓住。我在刷104题的时候就用上了这个理解104是求和题第一版我用int累加样例和正常数据都能过但后台如果出现很大的nint会溢出导致WA我提交前直接改成long long就跳过了一个经典巨坑。2.2 看到WA别慌先看评测状态再定位问题新手提交后最常见的反应是“居然WA了”然后盲目改代码东改一点西改一点最后越来越乱。正确做法是先看状态码再按状态码分门别类地排查。先放一张我整理过的评测状态对照表基本覆盖所有OJ的常见返回结果状态含义常见原因AC通过全部测试点无WA输出与标准答案不一致算法逻辑错误、输出格式问题、边界条件没处理好TLE超出时间限制算法复杂度过高、输入输出效率太低、死循环RE运行时错误数组越界、除数为0、空指针、递归栈溢出MLE超出内存限制数组开得过大、没有释放内存、递归层数过深PE输出格式错误空格、换行、大小写和标准答案不完全一致CE编译错误语法错误、头文件缺失、变量名冲突拿到状态以后我的排查顺序是先看CE把编译问题解决再看PE检查输出格式然后看RE优先检查数组长度和边界接着是WA这一步要回到算法逻辑和边界条件最后再看TLE和MLE这两个通常是算法选型的问题。这个顺序其实对应了“最表层的问题先解决”的原则避免你在有编译错误的时候还去纠结算法逻辑。提示很多OJ支持“自定义测试”或“提交历史”里的运行信息展示先利用这些功能拿到报错信息再去求助搜索引擎效率会高很多。不要一WA就到处问人不展示自己代码和状态的提问最后往往还是得自己排查。3. 复盘2.26的104、105、106三道题三道题的具体题面以我遇到的版本为例题号顺序代表了难度的小幅递增但都属于入门级别。下面按题型拆解每道题我都会讲清楚考点、参考代码、以及为什么这么写。3.1 104题多组输入求和别栽在输出格式上2.26的第一题是求和题题面大致是这样每行给出一个正整数n要求输出1到n的和。OJ通常会设计成多组测试数据输入直到EOF才结束。刚刷OJ的新手最容易在这里翻车因为课程作业里的题目一般只跑一组数据很多同学脑子里默认程序只读一次输入。我见过好几个同学写成了这样int n; cin n; long long sum 0; for (int i 1; i n; i) sum i; cout sum endl;这段代码如果只输入一组数据看起来完全正确。但OJ后台可能给了三组、五组甚至几十组数据你的程序只处理完第一组就退出后面全部没输出或者整个程序行为变得不可预期结果就是WA。正确写法是处理“多组读到EOF”#include bits/stdc.h using namespace std; int main() { long long n; while (scanf(%lld, n) ! EOF) { long long sum n * (n 1) / 2; printf(%lld\n, sum); } return 0; }这里有两个重点。第一用while处理多组输入C语言里是scanf ! EOFC里是while(cin n)Java里是while(in.hasNextLong())Python里是for line in sys.stdin不同语言的承接方式不一样但思路完全一致。第二为什么我用公式而不是for循环累加题目如果只要求小范围内的n循环没问题但一旦n可能会到10^9O(n)的循环绝对TLE而且int累加的结果早就溢出了。即便n没有大到超时用公式n*(n1)/2也是更稳的选择成本是O(1)。这题还有一个容易忽略的点n的类型。很多入门题数据范围写的是int能装下的值但求和结果会超过int范围。比如n100000时1到n的和是5000050000已经超过32位int的约21亿上限。所以我的代码里n本身用的是long long直接用64位避免中间运算溢出。这是104题真正的隐藏考点。3.2 105题求最大最小值的边界陷阱第二题是数组类。题面大致是第一行给一个正整数n第二行给出n个整数要求输出这n个数里的最小值和最大值仍然是有多组测试数据。这题核心考点是数组、循环、以及最大值最小值初始值的设置。有位新手朋友的写法是这样的int a[100], n; while (scanf(%d, n) ! EOF) { for (int i 0; i n; i) scanf(%d, a[i]); int max 0, min 0; ... }这里至少有两个问题数组长度开成100后台数据如果n10000就直接越界初始值设置成0也不安全如果输入的数全是负数max0会让答案出不来。更稳妥的方案是一边读一边比较最大值初始化为一个理论最小极值最小值初始化为一个理论最大极值或者直接都用第一个元素初始化。参考写法#include bits/stdc.h using namespace std; int main() { int n; while (scanf(%d, n) ! EOF) { int mn INT_MAX, mx INT_MIN; for (int i 0; i n; i) { int x; scanf(%d, x); if (x mn) mn x; if (x mx) mx x; } printf(%d %d\n, mn, mx); } return 0; }注意几个细节数组其实可以不开因为题目只要求最值不需要把数据都存下来边读边处理既省内存又少写代码初始化用INT_MAX和INT_MIN要记得包含对应头文件或者直接使用bits/stdc.h输出顺序题目要求先小后大我的printf里就先输出mn再输出mx这个顺序不能写反。这道题真正的考察点不在“会不会找最大最小”而在“能不能想到边界”。后台测试数据会包含n1的情况此时mn和mx应该同时等于唯一的那个数因为我的初始化是INT_MAX、INT_MIN然后循环里会把它更新成正确值所以能过。还包括所有数字相同的情况、全负数的情况、接近int极限的数值情况。你在本地跑样例时看不到这些提交前就要自己在脑子里过一遍边界清单。3.3 106题字符串统计从空格说到字符编码第三题是字符串类。我刷到的版本是输入若干行字符串要求统计每个小写字母出现的次数忽略其他字符输入一直读到EOF。这种题是后面很多字符串处理题的雏形可能的考察点是字符串读取、字符遍历、数组映射。先上一个容易踩坑的版本char s[100]; while (scanf(%s, s) ! EOF) { // 处理 s }scanf(%s)读取字符串时遇到空格或换行就会停止。如果输入是 hello worldscanf会只读到 helloworld被留在了缓冲区里结果统计就会少东西。所以在处理“含空格的整行”时C语言要用fgetsC要用getline而不是cin s。我2.26用的C版本#include bits/stdc.h using namespace std; int main() { string s; while (getline(cin, s)) { int cnt[26] {0}; for (char c : s) { if (c a c z) { cnt[c - a]; } } for (int i 0; i 26; i) { if (cnt[i]) printf(%c:%d\n, a i, cnt[i]); } } return 0; }这里最核心的技巧是用cnt[c - a]把字符a到z映射成数组下标0到25。很多新手不知道字符可以直接参与运算会写一长串的if-else判断“如果c是acnt[0]如果c是bcnt[1]……”不仅代码难看还容易漏。实际上ASCII码里小写字母是连续排列的c - a就能得到相对偏移量这是一个非常经典的字符计数套路。还有一个环境细节有些OJ的C版本比较老可能不支持C11的“范围for”for(char c : s)如果你的OJ报编译错误就把范围for改成传统forfor (int i 0; i (int)s.length(); i) { char c s[i]; ... }这属于老OJ的兼容性问题平时多了解一点真遇上了就不会卡住。这也是在OJ刷题特有的收获你会慢慢摸清每个平台的编译环境不同OJ对编译器版本、头文件支持、甚至main函数返回值的要求都可能存在差异提前知道这些能省下很多无意义的CE。4. 从WA到AC真实的Debug过程记录刷题最宝贵的不是最后那个AC而是中间那几个WA。2.26这三道题老实说我没有一遍全过104题还经历了两个WA才换来的AC。下面这些内容是我当天真实的调试过程写出来给大家当反面教材。4.1 第一次提交三个典型低级错误104题我第一版提交之后OJ返回WA。我当时第一反应是“不可能吧”然后回头一行一行看代码最后发现三个问题。第一个是多组输入确实处理了但sum的类型用的是int后台如果有大n数据累加值溢出变成负数输出就错了。第二个是我输出完没有换行printf写成了printf(%lld, sum)而OJ的逐字符比对里换行符也是答案的一部分。第三个是变量命名我写了一个全局变量也叫sum本地编译没问题但换到编译选项更严格的OJ上可能就会因为命名冲突报CE。这三个问题单独拿出来都很低级但合在一起就是典型的新手状态写得快、提交得也快缺少“提交前自检”这一步。这也是我想强调的OJ刷题真正练的其实是“写完不等于完自检才等于完”的意识。4.2 本地测试通过提交就错问题不在逻辑105题更典型。我在本地随便填了几组数据测试最大值、最小值都算对了结果一提交还是WA。我又检查了一遍逻辑发现逻辑本身没问题但输出顺序写反了先输出了最大值再输出最小值题目要求先输出最小值再输出最大值。样例里的数据恰好两个值离得比较远我也没仔细看顺序后台一对比就露馅了。这类“本地能跑、提交就错”的情况多数时候和算法无关而是格式和边界问题。我后来养成了一个习惯提交之前把样例输入复制到本地跑出结果后和OJ页面上的样例输出做一次逐字符对比用眼睛看把每个空格、换行都交代清楚。很多人觉得这一步多余但OJ刷题最效率的路径恰恰是先搞定格式再做逻辑。4.3 提交策略与节奏控制除了具体代码2.26我还想聊一个很多人忽略的问题提交节奏。有些同学喜欢写完就火急火燎地提交然后看着WA发呆接着改一行再提交再WA循环到把OJ当成“在线Debug工具”用。我自己现在的策略是“憋三下再提交”第一下本地Run一遍样例第二下闭着眼睛想一遍边界条件比如n0、n1、全是负数、字符串为空第三下检查一遍输入输出格式。三次自检都没有问题我才点提交。这套流程看起来很笨但长期统计下来我的平均提交次数明显下降了WA变少心情也稳定很多。另外如果一道简单题连续WA超过三次我的处理方式是不再硬刚去做下一道或者干脆出去转一圈。这听起来反直觉但很有效。连续WA会让你的思维固化在同一个错误假设上跳出来换换脑子回来常常一眼就看到问题。2.26的104题第三次提交前我没有继续改而是先去把106题的题面读了一遍再回来的时候突然想起sum可能溢出一改就AC了。5. OJ刷题避坑速查表与自检清单5.1 高频踩坑速查表下面这张表是我刷了一段时间OJ后整理的可以说每一条都交过学费。新手可以直接拿来当“踩坑查询手册”。症状可能原因解决办法样例能过但WA输出格式不一致逐字符对比样例输出检查空格、换行、大小写样例能过但WA边界条件遗漏补充最小、最大、重复、全负数等极端用例WA且答案看起来正确int溢出涉及累加、乘法时改用long longTLE用了O(n^2)但n很大换O(n log n)或O(n)算法降低循环嵌套TLEcin/cout没关同步加ios::sync_with_stdio(false)RE数组开得不够大按题目数据范围上限5来开数组RE访问了非法下标检查循环的上下界特别是-1和nMLE数组开得远超范围按需分配或改用STL容器PE多了一个空格或换行清理输出语句统一格式CE用了新标准语法但OJ是老标准改用传统写法避免范围for等新特性这里面有几条值得单独说一下。cin/cout的同步问题非常经典在C里cin和cout默认会和C标准IO同步导致速度变慢。在需要读入上万行数据的题目里不加ios::sync_with_stdio(false)很可能会TLE。我个人的习惯是C刷OJ直接写这个开关哪怕当前题目用不到提前加上也能避免后续换题时因为IO拖后腿。5.2 提交前的自检清单结合2.26的经验我整理了一个提交前自检清单基本能在30秒内完成但能帮你避免90%的无效提交多组输入的循环有没有写完整读到EOF后能正常退出。所有变量类型是否符合数据范围累加、乘法、负数场景有没有可能溢出。数组长度是否大于题目上限而不是刚好等于上限。边界条件有没有覆盖n0或1、字符串为空、数据全部相同、数据为最大最小值。输出格式是否和题目完全一致包括空格、换行、输出顺序、Case编号。本地样例和OJ样例输出是否完全一致用眼睛逐字符比过。照着这个清单顺手过一遍基本能把“低级WA”消灭在提交之前。这也是我在文章开头说的OJ刷题真正积累的不是题量而是这种自检意识。6. 刷OJ不止为了学分从高校题库到企业机考6.1 高校OJ和企业机考的差异很多同学刷高校OJ是为了平时分这没错但我更建议把眼光放远一点。现在不少技术岗位在线编程测评用的就是类似OJ的系统比如华为OD的OJ在线题库、各类企业的在线评测系统它们沿用了OJ的核心机制多组测试数据、隐藏边界用例、严格时空限制。在校期间把高校OJ刷扎实了后面面对企业机考至少不会在“环境适应”上吃亏。当然两者也有差异。高校OJ的题目相对偏向基础和纯算法比如104、105、106这种练的是基本功。企业机考通常会在算法基础上加一些实际场景的包装比如输入是一段日志或一堆任务输出是处理后的结果但剥离掉包装底层还是数组、字符串、排序、动态规划这些经典考点。所以我的建议是入门阶段先在高校OJ或同类型题库把基础题刷够再逐步过渡到企业真题题库这样梯度更合理。6.2 入门期怎么保持刷题节奏最后聊一个坚持层面的问题。很多人刷OJ最大的问题不是不会做而是坚持不下来。我自己的经验是每天不要给自己定太高的量。2.26那天我定的任务就是三道题104、105、106难度不大但也需要动脑半小时到一小时搞定比较容易坚持。如果有人第一天就定“今天刷十道”大概率第二天就不想打开了。我目前使用的节奏是“32”模式每天3道基础题维持手感2道稍微需要思考的题拓展思维。刷完以后不管AC没AC都花十几分钟写几行刷题日志记录今天的题号、题型、踩坑点、收获。这个习惯坚持下来以后我明显感觉自己的复盘能力也提升了遇到类似题目的时候能很快从记忆里检索出上次的教训。提示不要因为某道题一直WA就否定自己的能力。OJ的反馈只说“你的程序在某组数据上错了”不说“你不行”。把每个WA当成一次免费的小型Debug训练心态会平稳很多。2.26刷完104、105、106以后我在刷题日志里写下了一句很朴素的话入门题的坑全都在“你以为很简单”里。后来回头看这三道题虽然不难可它们覆盖了多组输入、类型溢出、初始化边界、字符串空格这些最基础的OJ素养而这些恰恰是后续一切难题的地基。新朋友刷OJ真的不用急着追求题量先把每一道基础题刷透、把每次WA的原因记下来比盲目刷新题有用得多。我现在的习惯是每周日把这一周的刷题日志翻出来看一遍看看哪些错误还在犯、哪些套路已经长进肌肉记忆里。等这套流程熟练以后再往华为OD这类企业机考题库迁移你会发现大部分题考的还是同样的底层功只是穿了一件场景的外衣。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →