资讯动态

OJ基础题刷题复盘:EOF、二维数组边界与字符串回文处理

发布时间:2026/10/16 5:41:41 来源:尧图企业网站定制
昨晚把OJ基础题库里的92、93、94三道题一口气刷完了顺手把U12单元也过了一遍。说实话这三道题在难度上一点都不吓人属于那种看一遍就能有思路写起来却总能卡一下的基础题。也正是这种题往往最能暴露一个人的编程习惯问题——变量初始化丢三落四、数组边界差一个下标、多组输入不知道怎么处理、字符串读入被回车坑了半天。把这次刷题过程完整记录一下包括每道题的思考路径、参考代码、踩过的坑以及U12单元里串联起来的知识点希望能给正在刷OJ基础题的朋友一点参考。1. 先说这次刷题的整体思路1.1 为什么专门刷OJ基础题很多人刷题喜欢直接上难题觉得基础题没有含金量我恰恰相反。尤其是OJ这类评测系统它和平时自己写代码完全不一样自己写代码只要功能对了就行OJ要求的是格式、边界、性能全部达标。基础题虽然考察的语法点单一但能把提交即通过这件事做到稳定靠的就是这些单一知识点上的条件反射。我对基础题的定位是三个词语法熟练度、边界敏感度、调试手感。语法熟练度不用说循环、数组、字符串这些写法必须不假思索边界敏感度是指遇到空数组单行输入最大值最小值这些情况时第一反应就知道要特殊处理调试手感则是在报错之后能快速定位是逻辑问题还是输出格式问题。这三个能力没有捷径就是靠一道道基础题喂出来的。1.2 92、93、94的题型与难度定位我当天定的目标是92、93、94三道题加U12单元。原因很简单这三道题在我用的那个OJ平台上属于同一个难度档正好组成一个小闭环92题是循环与EOF读入93题是二维数组遍历94题是字符串基础处理。U12又是把这些知识点串起来的综合单元。用一个小时左右集中刷完知识点之间能互相印证比零散地一天做一道效果要好得多也更容易形成完整的知识链条。题号核心考察点常见的卡壳位置难度预估92循环、多组输入、累加求和不知道EOF怎么用、sum忘记清零入门93二维数组、边界遍历四角重复计算、n或m为1时越界基础94字符串、双指针、字符过滤getchar的换行残留、大小写统一基础这三道题有个共同特点题目本身不难但全对需要抠的细节不少。例如92题如果只想着累加而忽略输入行数不确定就会卡在读入环节93题如果直接四边遍历把四个角算两遍答案就会偏大94题如果没处理空白字符回文判断就会误判。这些细节恰恰是OJ基础题最有练习价值的地方。2. 三道题逐一拆解思路与参考写法2.1 第92题多组数据求和与EOF读入这类题在各大OJ基础库里非常常见大概形式是输入若干行每行两个整数对每一行输出它们的和。题目没有告诉你到底有多少行只说输入到文件末尾。很多新手第一次见到这种描述会懵循环条件写什么难道要先读一遍数出来吗其实这就是EOF读入的标准场景。C语言里scanf函数的返回值是成功读入的参数个数如果读到了文件末尾返回的是EOF也就是-1。所以最稳的写法就是这样#include stdio.h int main() { int a, b; while (scanf(%d %d, a, b) ! EOF) { printf(%d\n, a b); } return 0; }这里有个细节值得展开说。很多人写多组输入时会用while(scanf(...) 2)这也可以但理解上稍有差别! EOF是只要不是读入失败就继续 2是只有两个数都成功读入才继续。对于这道题两者结果一样但在某些输入不规范的题目里 2更严格、更安全因为它能避免题目给了空行或者异常数据时程序乱跑。另一个容易踩的坑是sum变量清零。我第一次做类似题时习惯在循环里写sum a b然后在循环开头忘记把sum重置导致第二组数据在第一次结果上累加输出全部错乱。后来养成的习惯是凡是累加、计数、拼接这类变量要么在循环体第一行重置要么直接定义在循环体内部。定义在内部其实最省心因为每次循环天然是全新变量不会有残留。2.2 第93题二维数组周边元素求和93题大致是这样的输入一个n行m列的矩阵求矩阵最外圈元素之和。这里最外圈指第一行、第n行、第一列、第m列覆盖到的所有元素。看起来简单上手写就有两个隐患第一个是重复计算。如果按第一行 第n行 第一列 第m列的顺序逐个累加四个顶点会被加两遍。第一次写我老老实实加了结果样例输出比对不上加一个中间打印才发现了问题。第二个是行列只有一个的特例。如果n等于1那么第一行和第n行是同一行第一列和第m列也是同一列此时常规的处理方式会把同一行加两遍直接把答案翻倍。解决思路有两种。第一种最直观干脆老老实实写四个for循环分别读四条边但用条件判断把重复的角剪掉。#include stdio.h int main() { int n, m; scanf(%d %d, n, m); int a[105][105]; int i, j; long long sum 0; for (i 1; i n; i) { for (j 1; j m; j) { scanf(%d, a[i][j]); } } for (j 1; j m; j) { sum a[1][j]; // 第一行 if (n 1) { sum a[n][j]; // 第n行n1时跳过 } } for (i 2; i n; i) { sum a[i][1]; // 第一列排除已经算过的第1行和第n行 if (m 1) { sum a[i][m]; // 第m列 } } printf(%lld\n, sum); return 0; }第二种思路更取巧一点先初始化sum为全体元素之和再减去内部元素第2行到第n-1行第2列到第m-1列。这个思路好在不容易重复但需要先读入全部元素并累加一次内存和步骤上略多一点。两种写法我都试过个人推荐第一种原因在于它把边界问题拆成了一个个if判断读代码的时候非常直观。尤其当n或m等于1时每一个if都对应一个特判逐个推一遍就能确认没有遗漏。而第二种写法虽然简洁但要求你先想明白去掉内部之后剩下什么对新手来说更容易绕晕。2.3 第94题字符串回文判断94题是字符串处理常见形式是输入一个可能包含空格、标点、大写字母的字符串判断忽略这些干扰字符之后剩下的字母序列是否是回文。比如 A man, a plan, a canal: Panama 去掉标点和空格、统一大小写后是amanaplanacanalpanama正读反读一致就是回文。字符串题最大的坑不是判断逻辑而是读入。很多人用scanf(%s, s)它读到空格就停了后面字母全没进来判断结果自然不对。如果题目说可能包含空格正确读法是gets(s)或者fgets(s, sizeof(s), stdin)。不过gets函数在较新的编译环境里会被警告甚至禁用我建议直接养成用fgets的习惯#include stdio.h #include string.h #include ctype.h int main() { char s[1005]; char filtered[1005]; fgets(s, sizeof(s), stdin); int len 0; for (int i 0; s[i] ! \0; i) { if (isalnum(s[i])) { filtered[len] tolower(s[i]); } } filtered[len] \0; int left 0, right len - 1; int ok 1; while (left right) { if (filtered[left] ! filtered[right]) { ok 0; break; } left; right--; } printf(%s\n, ok ? YES : NO); return 0; }这里isalnum判断是否为字母或数字tolower统一转小写双指针从两端往中间走。整个过程只要过滤后的字符串长度是0或1直接就是回文代码里的while条件left right已经天然覆盖了这种情况。我最初写的时候犯过一个低级错误fgets读进来的字符串自带一个换行符\n我在过滤的时候没处理它导致过滤后的结果末尾带了一个换行。判断回文时首尾比较到最后一个字符一边是字母一边是换行直接判成NO样例怎么都对不上。查了快十分钟才发现是这里的问题。所以字符串题拿到手第一步永远是确认字符串末尾有没有隐藏的换行或空格这是这类题最容易翻车的地方。3. U12单元数组与字符串的串联复盘3.1 从单点题到综合题U12在练什么刷完三道题我再回头做U12单元感觉一下子顺畅不少。U12在我用的这套课程体系里是一个数组与字符串的综合练习单元里面不止有单点语法题还有几道把数组、循环、字符串操作串起来的小场景题。比如统计一段文本里每个字母出现的次数或者把一个字符串按空格拆成单词再倒序输出。这类综合题的价值在于92、93、94三题覆盖的语法点在U12里不再是孤立考点了。你写字母频率统计时既要用到字符串遍历94题的内容又要用数组下标记录次数93题的二维数组思想还要处理多组输入92题的循环框架。三个技能点在同一道题里交汇任何一个环节生疏都会卡住整个程序。我的体会是单元练习不需要题题都做但要保证每种题型至少独立写出来一遍。答应自己不看题解卡住二十分钟再看看完之后合上代码自己重新写。这个单元真正的作用是把零散语法点变成一套可组合的工具箱。3.2 单元里最容易被忽略的细节从U12的通过率来看新手最容易栽在三个地方。第一是数组长度。OJ题目经常会给数据范围比如最多100个字符但很多人就开char s[100]。结果字符串末尾要放一个\0有效存储其实是99个字符题目数据稍大一点就溢出报错还特别奇怪。我的习惯是题目给定的上限是多少数组就开上限加5甚至加10。一百个字符就开105一千个就开1005多出来的几个位置是给自己留的余量几乎零成本却能避免一大类问题。第二是缓冲区残留。用scanf读数字之后再读字符串输入缓冲区里会留下一个换行符如果后面直接fgets它会把这个换行读进去导致字符串为空或只有一个换行。处理办法要么在转换格式里加空格写scanf(%d , n)要么在fgets之前写一句getchar()把换行吃掉。我通常用后一种因为肉眼看得见、好排查。第三是输出格式。U12里有一种常见题要求输出单词1 单词2 单词3每个单词之间一个空格。很多人的做法是每个单词后面都跟着printf( %s)最后一个单词后面就多了个空格OJ直接给你PEPresentation Error格式错误。最稳妥的套路是第一个单词正常输出从第二个开始前面补空格或者用一个flag标记是否已经输出过第一项。4. 实测中踩过的坑与排查方法4.1 格式错误PE回车、空格和空行刷OJ的人对PE都不会陌生答案逻辑全对就因为多了一个空格或者少了一个换行被判格式错误。92题我遇到过一种典型情况题目要求每行输出一个结果结果之间不能有额外空行。我用printf(%d\n\n, sum)想视觉效果好看点OJ直接判PE。后来我养成了一个习惯提交之前先用眼睛检查一遍printf里的每个格式符。凡是题目没明确要求空行、尾部空格的地方一律不加严格按照样例的排版来。同时也提醒自己样例里看不到的地方也要遵守规则——很多题目样例只给两三组数据输出末尾有没有多余换行样例有时候根本看不出来但OJ会检查。最保险的做法就是最后一个输出后面也带换行但整个输出内容中间不要有多余空白行尾部不要有空格。4.2 数组越界与循环边界93题里最容易越界的写法是忘了二维数组下标从0开始还是从1开始。我习惯从1开始存既符合平时数学里的习惯也方便处理边界但代价是声明数组时必须多开一个长度写成int a[105][105]才能合法访问a[100][100]。还有一个很隐蔽的越界场景当n等于1时第一行和第n行是同一行如果代码里没有特判拿着第n行的下标去访问a[2][j]直接读到了未初始化的内存随机值加进sum里答案忽对忽错。这种问题最气人的地方在于它不一定会稳定复现有时候能过有时候不能过。排查方法只有一个把所有边界值列出来逐个手推一遍。4.3 调试三板斧打印中间值、分块测试、对照样例我调试这三道题用的方法非常朴素但对OJ这类黑盒评测非常有效。先说打印中间值。92题sum结果不对时我在循环体里加了一行printf(debug: %d %d %d\n, a, b, sum)提交一次把输出贴回本地立刻就能看出是哪一步开始错的。这里提醒一句调试输出提交前一定要删干净不然会被当作答案的一部分输出破坏最终格式。再说分块测试。93题我会单独构造n1、m1、n2、m2这种小矩阵手动算好答案放进去跑。这些边界用例一旦通过普通数据基本不会再出问题。最后是对照样例。很多人的习惯是把样例复制进程序看到输出和样例一致就提交。我会再多做一步在本地把样例的输出和自己想当然算出来的结果做对比。因为样例给出的输入往往刚好能避开边界问题所以样例过了不代表程序对。自己手动多设计几组数据再提交通过率会明显提升。5. 给初学者的刷题节奏建议5.1 一天刷多少题合适如果你也在刷OJ基础题我的建议是重质量不重数量。一天三五道题是合适的节奏前提是每道题都吃透了。什么叫吃透至少满足三点第一不看任何题解能把代码完整写出来第二能说清楚每个关键位置的写法为什么是这样第三能独立构造出至少一个边界测试用例并保证程序正确。一天刷十道题二十道题看起来很猛但大多数是看题解照抄抄完自己也没记住要点过两天遇到同类题还是卡。我见过不少同学刷题数量很多但一遇到略微变化的同类题就无从下手就是因为中间少了独立重写和边界构造两步。5.2 怎么把一道基础题刷出效果我个人的刷题流程是这样的先花五分钟读题理解清楚输入输出格式和范围然后不看任何资料自己尝试写写完跑样例通过后先不急着提交先问自己几个问题——如果输入很多行会不会崩如果只有一个元素会不会对如果数组开到最大会不会超时想清楚了再提交如果WA答案错误了第一时间打印中间值看关键变量如果AC通过了隔一天再重新写一遍。另外我强烈建议做一个错题记录。不用很复杂就一个表格记下题号、错因、正确写法。92、93、94这三道题我会做如下记录题号错因正确做法92sum忘清零累加变量定义在循环内部93四角重复计算剪角判断或逐边相加后去重94fgets残留换行过滤时排除\n和\r这个表格别看简单它会成为你刷题进阶的底稿。遇到同类型问题翻出来看一眼效率比重新踩一遍坑高得多。5.3 刷题之外的两个实用习惯最后再分享两个我实测很有用的习惯。第一个是格式化输入输出勤练。刷OJ最常用的是printf、scanf、fgets、sscanf这几个函数不要觉得太基础就跳过。字符串和数字混合输入时sscanf能从一段字符串里按格式提取多个变量写U12综合题能省掉很多手工解析的功夫。第二个是审题时先看输出格式再动手。不少人代码写完了才发现题目要求输出Case #1: 前缀或者要求保留两位小数只能回头改。我的习惯是读题阶段就把输出格式圈出来明确每一行要输出什么、以什么间隔先想清楚再写代码写完之后就不会因为格式问题来回提交了。基础题刷得不快但每道题都尽量多写一个变体——92题我会想想如果改成多组字符串怎么处理93题我会想想如果改成求两条对角线之和怎么写94题我会想想如果要求输出所有回文子串怎么扩展。这些积累在后面的综合题里都是直接回报U12单元就是这么被吃透的。

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

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

免费获取报价 →
↑