资讯动态

OJ基础题刷题指南:从分支循环到边界与输入输出陷阱

发布时间:2026/10/1 22:22:57 来源:尧图企业网站定制
2月13号晚上我窝在宿舍里把OJ上的119、120、124三道基础题重新过了一遍。说实话这三道题放在整个题库里并不起眼难度也谈不上高但每次假期快结束的时候总能看到一批人在答疑区问几乎同样的问题本地跑得好好的一提交就是Wrong Answer样例能过边界一测就崩甚至还有人连输出格式都搞不明白。刷OJ的第一道坎从来不是算法而是你对输入输出、判题规则和基础语法细节的掌握程度。这篇文章不是那种贴个代码就跑的题解我想从为什么刷这三道题每一道题背后的考察点提交时容易踩的坑三个角度拆一遍顺便把我在杭电OJ、郑轻OJ这类平台上踩过的一些真实教训一起写进去。适合三类人看刚接触OJ、连多组输入都不太清楚的新手刷到基础题卡住、样例过了却一直AC不了的半新手以及想帮学弟学妹答疑、需要把细节讲透的老手。先说明一下这里的119、120、124是题号不同平台编号对应的题目并不一样但基础题区绝大多数都是把分支、循环、数组/字符串这三类题型循环排布我以最典型的题目形态来展开思路是通用的。1. 先搞清楚119、120、124到底在考什么1.1 三道基础题在OJ题库里的位置每个OJ平台都会有一个基础题或者入门题分区题号比较小的那一批通常在100到200之间就是给新手练手用的。我刷了这么多平台后发现这区间里的题目有一个共性不考复杂算法不考高级数据结构核心就三件事——读懂题目、写对语法、猜中判题系统的脾气。119、120、124这三道题恰好踩在三个最典型的训练点上119题通常属于分支结构题常见形态是判断闰年、成绩等级、分段函数。这类题考察的是条件表达式的严谨性尤其要注意边界条件的覆盖范围。120题通常是循环结构题常见形态是打印三角形、求水仙花数、输出九九乘法表。这类题考察的是循环变量的起始位置、结束条件以及换行符的位置。124题一般是数组或字符串题常见形态是字符统计、逆序输出、数字拆分。这类题考察的是对连续存储空间的遍历能力以及C语言里字符串输入函数之间的细微差别。很多新手有个误区觉得题目简单就不值得认真做直接CtrlC一份答案交上去AC了就万事大吉。但实际上这三道题恰好是后面所有题目的地基119考的是条件的边界120考的是循环的边界124考的是数据的边界。边界问题正是OJ从入门到进阶一路上最阴魂不散的东西。1.2 三道题的类型划分与训练目标先把三道题的训练目标用表格梳理一下方便你在刷题的时候对照自查题号核心考点典型形态提交时常犯的错通过后你应该掌握的能力119分支结构闰年判断 / 成绩等级条件没写全、else if被break打断条件覆盖率达到100%120循环嵌套输出三角形 / 九九表死循环、换行输出错位精确控制循环次数124字符/数组统计统计大写小写数字个数gets被禁用、读到换行符熟练处理字符串输入我个人的经验是这三道题如果能在不看题解的情况下独立写出AC代码你的C语言基础就可以说基本过关了。如果还需要靠百度才能拼凑出来那说明至少有两个薄弱环节需要补一个是条件判断的自然语言转代码能力一个是循环执行过程的脑内模拟能力。接下来我按题号依次拆解。2. 119题分支逻辑的一分价钱一分货2.1 闰年判断的标准解法和代码以最常见的119题形态输入年份判断是否为闰年为例。闰年的定义其实就两句话能被4整除但不能被100整除或者能被400整除。我第一次刷这类题的时候傻乎乎地把所有条件写成了并列的if结果四个if全执行了一遍变量被反复改来改去。正确的做法是用短路逻辑把两个条件用或运算连起来#include stdio.h int main() { int year; while (scanf(%d, year) ! EOF) { if ((year % 4 0 year % 100 ! 0) || year % 400 0) { printf(yes\n); } else { printf(no\n); } } return 0; }注意这里用了while (scanf(%d, year) ! EOF)。很多OJ题目描述里会写输入数据有多组每组占一行但同样有很多题目不写这句话。判题系统跑测试用例的时候会往标准输入里连续塞好几组数据如果你的程序只处理一次就返回了后面的数据直接被忽略结果就是大概率WA。所以我的习惯是只要题目没说单组输入一律写成循环读入。即使题目确实只有一组数据循环读入也不会出错最多就是不必要地多等一次EOF。2.2 分支题最容易踩的三个坑第一个坑是把else if和if混用。有些同学写成绩等级判断时是这样的if (score 90) { printf(A\n); } if (score 80) { printf(B\n); } if (score 70) { printf(C\n); }这份代码跑起来会发现考95分的同学既拿了A又拿了B还拿了C。原因就是多个平行的if会顺序执行条件匹配完不会自动停下来。正确写法是else if串起来让分支结构只有一个入口被执行。第二个坑是忽略了等于号。条件判断里和差一个等号影响的可能就是临界值的那一分。典型的例子题目要求大于等于90分为优秀你写成了score 90那刚好90分的人就被错误地划到下一个等级去了。OJ测试用例设计者非常喜欢卡这种临界值他们专门会把边界分值放进测试数据里就等着你漏掉等号。第三个坑是忘记覆盖异常输入。有些题目会约定输入数据在一定范围内比如分数在0到100之间这种约定你可以放心大胆地忽略异常情况。但如果题目没做约定你就需要自己补一层判断否则输入一个负数或者超过100的数时程序的逻辑会产生未定义行为。虽然OJ的测试数据一般不会故意刁难你但养成防御性编程的习惯没有坏处。注意分支结构里还有一个容易被忽略的细节——switch语句里的break不能漏。我见过不少同学在OJ上用switch写等级判断漏掉break之后所有case全部贯通执行结果一个简单的分支题能错得面目全非。如果你的代码里用到了switch提交前务必检查每个case分支末尾的break。3. 120题循环嵌套里最容易出事的不是循环是换行3.1 三角形输出的代码与执行过程120题如果是输入n输出n行由*组成的直角三角形代码非常经典但执行过程值得仔细讲一遍#include stdio.h int main() { int n; while (scanf(%d, n) ! EOF) { for (int i 1; i n; i) { for (int j 1; j i; j) { printf(*); } printf(\n); } } return 0; }外层循环for (int i 1; i n; i)控制行数从第1行到第n行内层循环for (int j 1; j i; j)控制每一行星号的数量第几行就输出几个星号。这里的关键是内层循环的上界用的是外层的i而不是写死的n。如果你把内层写成j n那输出结果就会变成n行每行都是n个星号变成一个实心矩阵。运行到第3行的时候外层i等于3内层j从1到3依次输出三个星号结束后外层执行printf(\n)换行。整体输出就是* ** ***注意内层循环负责输出星号外层循环只负责换行两者的分工必须清晰。很多人把printf(\n)放在了内层循环里结果每一颗星后面都换了一行输出变成一列竖线。这种错几乎每个新手都犯过好在也最容易通过对比样例输出发现。3.2 边界条件和格式化输出才是得分关键先说说死循环的排查思路。循环写死之后程序会卡在某个地方一直运行提交结果通常是Time Limit Exceeded也就是超时。我自己排查死循环时习惯在循环体里加一个临时输出打印当前变量的值例如printf(debug: i%d\n, i);跑一遍看它停在哪里。大多数情况下问题出在循环变量的更新上写了i却写成了i--或者循环条件用了i 0但初始值是0循环体永远进不去。前者死循环后者直接跳过。再说说格式化输出的问题。OJ判题不是拿肉眼看输出而是把你的程序输出和标准答案做逐字符比对多一个空格、少一个换行都是Wrong Answer。我在三个不同OJ平台上都见过同一类情况第一行输出对了第二行开始前面多了一个空格。原因往往是循环体里先打了空格再打星号但题目要求的是行首不留空格。解决办法只有一个——先用样例输入在本地跑一遍用肉眼逐字比对输出尤其注意行尾有没有多余空格行间有没有多余空行。另外内层循环结束后再输出换行这件事的顺序不能乱。有些题目会在三角形之后多一个空行要求每组输出之间空一行那么你得在每组数据全部处理完之后再额外输出一个换行符而不是在每一行后面简单追加一个。区分每行结束后的换行和每组结束后的空行是最容易混淆的地方做题时要把题目原文里的输出描述读三遍再动手。提示在OJ上调试循环题时有一个很实用的技巧——把输入数据缩小到极限比如n1或者n0。n1时输出应该只有一行一个星号n0时理论上什么都不应该输出。用这样的边界值测一遍循环的初始条件和结束条件有没有写对一目了然。4. 124题字符统计背后的字符串输入之争4.1 从gets到fgets的写法演进124题如果是输入一行字符串统计其中大写字母、小写字母、数字字符的个数这道题的精髓不在统计逻辑而在字符串的输入方式。老教材和很多网络题解上都用的是gets()#include stdio.h #include string.h int main() { char s[1001]; while (gets(s) ! NULL) { int upper 0, lower 0, digit 0; for (int i 0; s[i] ! \0; i) { if (s[i] A s[i] Z) upper; else if (s[i] a s[i] z) lower; else if (s[i] 0 s[i] 9) digit; } printf(%d %d %d\n, upper, lower, digit); } return 0; }这段代码在老的GCC编译环境下可以AC但现在越来越多的OJ平台启用了更高版本的编译标准gets()已经被标准库标记为移除函数——因为gets()在读入时不检查缓冲区边界存在严重的安全隐患。在部分新平台上这段代码直接编译不过报错信息是warning: the gets function is dangerous and should not be used严格模式直接变成编译错误。所以我现在写字符串读取一律用fgets()替代虽然它比gets多了两步操作第一步要从stdin读入第二步要手动去掉末尾的换行符因为fgets()在读入一行后会把这个换行符也存进字符串里如果不处理统计字符时就会多出一个不可见字符#include stdio.h #include string.h int main() { char s[1001]; while (fgets(s, sizeof(s), stdin) ! NULL) { s[strcspn(s, \n)] 0; int upper 0, lower 0, digit 0; for (int i 0; s[i] ! \0; i) { if (s[i] A s[i] Z) upper; else if (s[i] a s[i] z) lower; else if (s[i] 0 s[i] 9) digit; } printf(%d %d %d\n, upper, lower, digit); } return 0; }strcspn(s, \n)这个函数的意思是找到s中第一次出现换行符的位置返回这个位置的下标把那个位置上的字符手动改成字符串结束符\0换行符就被删掉了。这一步是我最想强调的因为很多人改用fgets后始终AC不了就是因为忘记处理这个换行符统计数字符总数时总是多1。4.2 字符统计里必须想清楚的ASCII边界统计字符时我见过太多人用数字常量去记忆ASCII码比如if (s[i] 65 s[i] 90)判断大写字母。虽然ASCII码表里大写A是65、小写a是97、数字0是48但直接在代码里写魔数会严重降低可读性而且一旦记错一个数字整个判断全盘皆错。建议统一写成字符常量形式比较s[i] A s[i] Z判断大写字母s[i] a s[i] z判断小写字母s[i] 0 s[i] 9判断数字字符这样做的好处是代码自动适应各种字符编码比如ASCII的连续排列保证了A到Z是紧挨着的而像EBCDIC这类编码不一定如此字符常量写法至少能保证逻辑表达清晰。还有一个经常被忽略的点中文字符的ASCII值是负的。如果你统计的是英文字符那么char类型的取值在-128到127之间中文字符落在负区间。有些同学用else兜底统计其他字符时把中文字符也算进去了导致输出总是比预期多。如果题目明确只统计英文字母和数字最好把统计逻辑写清楚不认识的字符一律跳过而不是用else包罗万象。注意用scanf(%s, s)读字符串时遇到空格就停了如果题目输入的字符串包含空格比如Hello Worldscanf只能读到Hello后面的World被丢弃。判断题目是否可能包含空格看题目描述里一行字符串这几个字就够只要没说是一串连续字符一律默认有空格用fgets不用scanf。5. 从本地跑通到OJ判AC中间隔着这些坑5.1 提交流程和平台判题逻辑先把OJ的判题流程捋一遍。你点击提交后平台不会运行你本地编译好的程序而是把你提交的源代码放到一台Linux服务器上用服务器自带的编译器重新编译然后跑编译好的可执行文件输入数据从测试文件读入输出结果和标准答案做比对。这意味着你的代码在本地Windows上能跑不代表在OJ的Linux环境上能跑。最常见的跨平台坑是int类型长度。在32位系统上int和long都是4字节范围是-2147483648到2147483647在64位Linux上long是8字节int仍是4字节。如果你的题目数据范围超过20亿用int就会溢出需要改用long long至少8字节。判断方法很简单题目描述里经常给出数据范围比如n 10^9看到这个数量级就避开int直接上long long。另一个和平台强相关的坑是main函数的返回值写法。用void main()在老版本编译器上能通过但严格的编译环境下会报错。一律写int main()并在函数末尾return 0;这是最保险、最跨平台、最符合C标准的写法没有之一。5.2 WA/PE/RE/TLE排查实录我在OJ上刷题这几年把评测结果大概分成六种每种都对应不同的代码症状下面用表格整理一下方便你提交后对照排查评测结果含义常见原因排查方向ACAccepted通过无无需处理WAWrong Answer答案错误算法逻辑不对 / 边界未覆盖重新读题造极端数据自测PEPresentation Error格式错误多了空格、空行、少换行逐字比对输出和样例RERuntime Error运行时错误数组越界 / 除零 / 栈溢出检查数组长度和递归深度TLETime Limit Exceeded超时循环死循环 / 算法复杂度过高缩小数据规模排查循环条件MLEMemory Limit Exceeded内存超限数组开太大 / 动态分配未释放缩减数组长度用静态数组WA是最让人头疼的因为样例过了但答案错误说明你的代码在某个隐藏测试数据上出了问题。我的排查顺序是这样的先重新读一遍题目把输入范围圈出来然后给程序喂几组边界值——比如0、1、最大值、最小值、空输入。举个实际例子有一次我写分段计费题样例输入和输出完全正确WA了一下午都没找到问题最后把输入数据的最大值套进去一算发现超过某个阈值后输出变成了负数原因就是int溢出了。改成long long后立马AC。RE里面数组越界是最常见的。C语言的数组越界不会像Java那样抛出异常而是可能改写了隔壁变量的内存导致程序在某个看似无关的地方崩溃。排查这种问题最直接的办法是把所有和数组循环有关的检查一遍在C语言里数组长度为100时合法的下标是0到99循环里如果写成了i 100就是越界访问。把循环条件背成i 数组长度而不是i 长度-1能少踩不少坑。PE则是最冤枉的一种错误——你答案全对只是格式不对。当你看到PE时别急着重写代码先去检查行尾空格和行间距。我曾经做一个输出矩阵的题结果PE了三次原因仅仅是我在每行末尾都多打了一个空格。判题系统对空格的容忍度是零它不会帮你清理行尾空白。6. 2月13日前后怎么把基础题刷出效果6.1 刷题节奏和时间安排2月13日这个时间点说尴尬也尴尬——寒假接近尾声学校还没开学但你大概率已经意识到假期前的flag又倒了一半。我自己的经验是与其焦虑还有多少题没刷不如把刷题切成小块用固定的节奏推进。三道基础题如果集中刷大概需要三到四个小时其中119题可能20分钟就搞定了120题值得花40分钟好好研究循环边界124题你可能会在字符串输入上磨掉一两个小时——这都很正常。我建议的节奏是这样的第一轮先完全独立做题不看题解、不百度哪怕卡住了也要先自己硬扛30分钟。因为OJ基础题的难点从来不是算法而是能不能把模糊的题目描述翻译成清晰的代码逻辑这个翻译能力只能靠主动思考练出来。第二轮再把通过的代码和网上高质量题解对比看看有没有更简洁的条件写法、更安全的输入函数。第三轮做一个复盘笔记记录自己第一次做错的原因、错误输出长什么样、怎么定位并解决的。如果你打算每天晚上固定刷题我比较推荐三道题一轮 一道挑战题加练的组合119、120、124这样的基础题热身再选一道稍微带点技巧的题目比如需要一点数学推导的题练脑子。这样既保证了基础手感又不会让刷题变成无脑重复。6.2 错题记录模板和复盘方法刷题不复盘等于白刷。很多同学把OJ当题库刷AC一道扔一道到期末复习时发现自己连基础题都不会了就是因为缺少沉淀。我自己整理了一套适合OJ刷题的错题记录模板按下面几个字段记录即可题号与题型例如119 分支结构方便后期按题型归类复习。错误状态WA、RE、TLE还是PE。记录这个能看出你自己的薄弱环节分布。错误原因一句话说清楚例如漏了score 90的等号或gets函数导致编译错误。修正方案把改前后的关键代码片段都贴进去。隐藏陷阱这道题有没有边界值、格式要求之类容易被忽略的细节。每周末花20分钟翻一遍本周的错题记录你会发现某些错误持续出现——比如你总是不记得处理fgets的换行符或者总是在循环里多打一个空格。知道自己的惯性错误有哪些比稀里糊涂刷一百道新题有用得多。这也是我在OJ上面花了无数个小时后最深的感悟。最后说个我自己的土办法在每个OJ平台上我都把基础题区从前往后刷两遍。第一遍求AC第二遍求速度。第二遍刷的时候我会给自己限时一道题10分钟内必须跑通并提交通过。限时的好处是逼你形成条件反射——看见多组输入就写循环看见输出每个结果占一行就习惯性加换行。这种条件反射听起来不起眼但它会在你后面做复杂题目时自动帮你处理掉低级细节让你能把脑力集中在算法本身上。基础题刷到这种程度才算是真正把题做透了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑