☰
OJ基础题刷坑指南:多组输入、格式化输出与数组边界处理
2026/10/1 10:55:59 网站建设 项目流程

记得三月初那会儿,刚把OJ(Online Judge)的基础题刷到三位数,正好卡在116、117、118这三道题上。说实在的,这个阶段的题目不算难,但特别有意思——它们不像后面的算法题那样需要绞尽脑汁想状态转移,也不像前面的水题那样无脑输出Hello World,而是刚好卡在"你刚学会语法、还没形成算法思维"的尴尬节点上。很多人在这个阶段刷题靠蒙,看完答案觉得懂了,换个题目又不会了。这其实就是典型的基础没打牢。

我自己刷这三道题的时候,感受最深的一点是:基础题考的不是你会不会某个高深算法,而是你对问题拆解的基本功。题目本身可能只是让你处理几个数字、排个序、反转个字符串,但背后真正训练的是你读题、拆解、边界处理、还有调试的能力。这些能力看着不起眼,但后来刷到一百多道题之后回头看,真正让你AC速度拉开差距的,恰恰就是这些底层东西。

这篇文章我就拿116、117、118这三道基础题作为引子,聊聊OJ基础题阶段该怎么刷、怎么想、怎么避坑。如果你也正巧刷到这个编号区间,或者刚开始接触OJ没多久,这篇文章应该能帮你少踩几个坑。

1. OJ基础题阶段的核心逻辑:题目编号背后的学习路径设计

先聊聊这三道题所处的阶段。大多数OJ平台的前200道题,编号并不是随机排列的,而是根据难度和知识点的梯度来设计的。拿郑轻、杭电这类平台来说,基础题区间一般是按照"顺序结构 → 选择结构 → 循环结构 → 数组 → 字符串 → 函数 → 结构体"这样的路径来铺设的,每几道题集中训练一个知识点。

116、117、118如果落在100~120这个区间,通常是基础语法已经过完一轮之后的小进阶。这时候的题目特征很明显:

  • 输入输出格式开始有了讲究。不再是无脑给几个数让你直接求和,而是会有多组数据、带个数的多组数据这种常见套路。
  • 算法本身不难,但讨厌细节。比如让你算个平均值、找最大值,很多人卡住不是因为不会算,而是因为不知道浮点数格式化输出的精度怎么控制。
  • 开始考察你"读题能力"。题面会稍微绕一点,可能带个实际场景,比如按成绩排序、统计身高,你需要先把问题抽象成纯粹的计算任务。

这个阶段最重要的认知是:你刷的不是题,是熟练度。基础语法你可能课堂上都听过,但OJ题最狠的地方在于它逼着你自己动手把代码敲出来,并接受机器的无情评判。语法课上你可能觉得"这个我会了",但真到了OJ上,一个分号漏了、一个变量类型错了、一个数组范围开小了,全部都会让你WA(Wrong Answer)或者RE(Runtime Error)。

所以这三道题的核心意义,是帮你把"我大概明白了"变成"我确实能写对"。这是从听懂到会做之间最关键的一段距离。

1.1 这个阶段常见的三道典型题类型

虽然不同平台的116/117/118具体题目不一样,但从我刷过的多个平台来看,基础题区间的题目万变不离其宗,基本跳不出这几类:

题目类型特征核心考点
输入输出变形题涉及多组输入、EOF结束、特定终止条件输入格式解析、循环终止判断
数学计算题求平均、求和、进制转换、判断质数数据类型选择、格式化输出、边界处理
数组处理题排序、找最值、逆序输出、标记统计循环遍历、下标边界、交换逻辑
字符串基础题字符统计、反转、大小写转换字符串API、ASCII码处理

这三道题大概率是从这些类型中各选几道组合,让你在一天内连续处理不同类型的任务,强迫你切换思维。比如116可能是输入变形题,117是数学计算,118是数组处理——就是因为类型不同,所以放在一起刷才有了训练意义。如果三道题都是求和,那刷完一道就没什么新东西了。

1.2 为什么要把基础题"成套"地刷

很多人刷OJ有一个坏习惯:今天刷一道数组的,明天刷一道字符串的,后天又去碰难的动态规划。这样刷表面上看数量在涨,但知识点没法形成体系。OJ基础题编号里相邻的几道题,往往是同一知识点在不同场景下的变体,你今天做116、117、118,如果恰好涵盖了"输入处理+计算+数组",那这一组刷完,你对"如何读入数据→如何处理→如何输出结果"这个完整链路会形成肌肉记忆。

这种肌肉记忆特别重要。到了后面刷复杂题的时候,你会发现大家差距往往不在算法思路的复杂度上,而是在写代码的速度和准确率上。同样的思路,有人十分钟写完提交AC,有人写了一小时还因为各种细节点WA——差的不是智商,是基础题阶段有没有真正养成"一次写对"的习惯。

2. 三道题的拆解思路:从读题到AC的思考链路

这一节我根据最常见的平台题目类型,把116、117、118的解题思路做一次完整拆解。这类题目的通用思考链路可以归纳为四步:读题提取输入输出 → 构思数据结构 → 确定处理逻辑 → 处理边界与格式。

2.1 第116题:输入格式陷阱与EOF的坑

这类题通常是多组输入。经典格式是:每组给两个数,让你输出它们的和,但题面不说一共几组,只说"输入包含多组数据,每组占一行,直到文件结束"。

很多新手在这道题上的第一反应是写一个死循环,然后卡死在终端里。正确的理解是:你需要读一次,判断有没有读到,读到就处理,没读到就退出。C语言里通常写成while(scanf("%d %d", &a, &b) != EOF),C++里是while(cin >> a >> b)。

这里的核心思考是:多组输入的本质是将"反复做同一件事"交给循环去完成,循环终止条件由上一次读取的返回值决定,而不是由你预设一个计数器决定。

当时我还踩过一个细节:有的题目要求读到"0 0"就结束,这种情况就不能用EOF判断了,必须在循环体内部加一个if(a == 0 && b == 0) break;。很多人漏掉这个判断,导致最后提交时输出多了一大段内容,白白吃了一个WA。

2.2 第117题:格式化输出与精度控制

这道题大概率是计算类题目,比如求平均成绩,保留两位小数。这类题本身计算量很小,但输出格式卡得严。

先说保留两位小数的几种做法:

  • C语言用printf("%.2f", avg);,最简单,推荐。
  • C++用cout << fixed << setprecision(2) << avg;,需要包含<iomanip>头文件。
  • 如果要用Python,print(f"{avg:.2f}")或者print("%.2f" % avg)。

这里有一个特别容易被忽视的点:很多题目要求的"四舍五入"在不同语言里的处理逻辑不完全一样。C语言的printf是四舍五入,Python的round在Python3里是银行家舍入,遇到恰好0.005这种边界情况会有偏差。所以刷OJ的时候,尽量固定用一种语言,并且熟练这个语言的格式化规则,不要临时换。

还有数据类型的坑。计算平均值时,很多人声明int sum = 0,然后直接sum / n,结果整数除法把小数全丢了。正确做法是先把其中一个操作数转成浮点:(double)sum / n或者sum * 1.0 / n。这个细节几乎每个刚刷OJ的人都栽过,一旦你在输出阶段发现结果永远是整数,第一反应就应该检查除法两边是不是有浮点数。

2.3 第118题:数组处理与边界索引

这道题常见题型是逆序输出或者找最值。比如让你读入n个数,然后逆序输出这些数。

逆序输出的核心思路有两种:

  1. 全部存进数组,然后从n-1下标往0输出。
  2. 读入时不按顺序存,第一个读入的数存到数组最后一个位置,边读边倒着放,最后顺序输出。

第一种更直观,也是大多数人的选择。但这个题目真正考察的其实是你对数组下标从0开始这个事实的敏感度。存n个数时,最后一个数的下标是n-1,循环要写成for(int i = n - 1; i >= 0; i--),而不是for(int i = n; i >= 0; i--)。后者会直接访问一个越界地址,轻则读到垃圾值,重则RE。

如果是找最值问题,经典套路是:设第一个数为当前最大值,然后从第二个数开始逐个比较。注意边界:如果输入只有一个数,最大值就是它本身,循环应该从下标1开始,且循环次数是n-1。

我还遇到过一种变体:要求输出最大值及其第一次出现的位置。这种题需要用一个变量记录下标,在"当前值比最大值大"的条件下更新下标。这里有个小技巧:判断条件写>还是>=,直接决定了你输出的是第一次出现位置还是最后一次出现位置。题目要是没明确说,标准答案一般取第一次出现,所以用>。

2.4 从三道题提炼出的通用解题模板

做完这三道题后,我总结了一套适用于基础题的通用分析模板:

  1. 先确定输入结束条件:是EOF、是特殊输入组合、还是先给了总数n。这个不确定就直接动手写,十有八九要返工。
  2. 再确定数据存储方式:需要存数组吗?还是边读边处理就行?能用滚动变量解决的,不额外开数组。
  3. 写处理逻辑前先想边界:只有一个数据时的行为、没有数据时的行为、最大值最小值时的行为。
  4. 格式化输出前,对着样例看一眼规则:空格、换行、小数点位数、结尾是否有额外空格——这些OJ都是严格比对的。

这套模板后来在我刷后面几百道题时反复用到,强烈建议你也把它内化成自己的做题习惯。

3. 完整实操记录:从空白编辑器到AC的全过程

光讲思路不够,这一节我用一个和116/117/118难度相当的完整例子,带你走一遍真实的实操流程。我们就拿"多组输入求平均值,保留两位小数,直到输入0结束"这道题来说。

3.1 环境准备

我用的本地环境是VS Code + GCC(Windows上用MinGW,Linux/macOS自带的gcc就行)。OJ平台一般支持C、C++、Java、Python等语言,我建议新手统一用C语言入门,原因是:

  • C语言语法简单,没有被封装成黑盒的高级特性,你能清楚知道每行代码在做什么。
  • OJ题面的标准答案多以C/C++为基准,对内存、运行时的限制更直观。
  • 后面学数据结构和算法时,C语言写起来最接近底层逻辑,不会因为语言特性干扰思路。

选语言这事没有绝对标准,但"先死磕一门语言做算法题"这个原则不会错。频繁换语言对新手是灾难——你会在编译器报错上浪费大量时间。

3.2 读题与样例分析

题目可能长这样:

输入包含多组测试数据,每组一行,包含若干个整数,以一个负数标记该组数据结束。每组数据中负数的位置不固定。当第一组数据就是负数时,程序结束。输出每组数据的平均值,保留两位小数。

读题的要点是逐字读,别跳。题目里的每个条件都可能影响代码逻辑,比如"第一组数据就是负数时程序结束",意味着你不仅要处理组内的负数标记,还要区分"组内负数"和"整个输入的开始就是负数"这两种情况。

我习惯在草稿纸上先写三组虚拟输入和对应的期望输出:

输入: 1 2 3 -1 4 5 6 7 -1 -1 输出: 2.00 5.50 (无输出)

这能帮你确认对题目逻辑的理解是否和OJ一致。

3.3 编写代码

下面是我实际提交通过的代码,用的C语言:

#include <stdio.h> int main() { int first, x; while (scanf("%d", &first) != EOF) { if (first < 0) { break; // 第一组输入就是负数,整个程序结束 } int sum = first; int count = 1; while (1) { scanf("%d", &x); if (x < 0) { break; // 当前组结束 } sum += x; count++; } printf("%.2f\n", (double)sum / count); } return 0; }

几个关键点说一下:

  • 外层while(scanf(...) != EOF)处理"多组数据"这个大前提。一旦输不出来,整个循环自然结束。
  • 第一个数单独读,边判断是否为负数,又为了后面统计count从1开始做铺垫,这样处理既简洁又不会漏掉第一个数。
  • 内层while(1)配合break处理组内的负数终止标记。这里有同学喜欢用while(scanf("%d", &x) && x >= 0)来写,也可以,但要注意当x是负数时它已经读了数据,不能在循环体内再额外读一次。
  • 平均值直接(double)sum / count,因为强制转换优先级在除法之前,结果是浮点除以整数,自动转成浮点运算。

3.4 编译运行与本地测试

写完后先在本地编译,确认没有编译错误:

gcc average.c -o average

然后手动输入测试数据:

1 2 3 -1 4 5 6 7 -1 -1

程序输出:

2.00 5.50

这里有个细节:如果是在终端里手动输入,输入完-1按回车后,程序会不会结束取决于你是否触发了EOF。因为在Windows终端里,直接回车不会得到EOF,你需要按Ctrl+Z再按回车,Linux里是Ctrl+D。很多新手在本地测了半天,发现程序"卡住不动",其实是终端没有发送EOF信号,不是程序死循环。知道这个小坑,能少浪费很多时间。

3.5 提交并处理反馈

本地测试通过后,登录OJ平台提交。如果返回AC(Accepted),恭喜,这道题过了。如果出现WA、PE之类的,不要慌,按后面的排错流程走一遍。

我第一次提交这道题时,其实是先拿到的PE(Presentation Error)。原因是我最后多输出了一行空行。OJ的判定中PE和WA虽然都算"没AC",但PE通常意味着你的输出内容基本正确,只是空格、空行等格式没对齐。看到PE就检查格式,千万别去怀疑算法。

3.6 用一个循环数组版本做对比

相比之下,如果题目是"先给n,再读入n个数,逆序输出",代码会更简单:

#include <stdio.h> int main() { int n; scanf("%d", &n); int a[1005]; for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } for (int i = n - 1; i >= 0; i--) { if (i == n - 1) { printf("%d", a[i]); } else { printf(" %d", a[i]); } } printf("\n"); return 0; }

这个例子想说明两件事:

  1. 数组开多大:题面没说n的范围时,你按常识开一个足够大的固定数组,比如1005。如果范围明确是0 < n < 1000,开1005就够;如果明确是n <= 10^6,就要开1000005,同时留意栈空间。
  2. 输出格式空格控制:很多人喜欢在循环里每个数后面跟着输出一个空格,最后输出一个退格符\b。这种做法在本地可能显示正常,但在OJ的严格判定下容易出问题。更稳妥的做法是:第一个数前不输出空格,后面的数都在前面输出空格。

4. 常见报错与坑点排查:从PE到RE的心路历程

OJ判题系统的返回结果对于新手就是天书,看到WA第一反应就是"我到底哪里错了?"。这一节我把基础题阶段最常见的几个报错整理出来,附带排查思路。

4.1 WA(Wrong Answer):答案错误

WA是最让人头疼的,因为题目不告诉你哪个测试点错了。基础题WA的原因,按出现频率排大概是:

  1. 没有加换行。很多新手写完输出就结束,忽略了\n。OJ比对的内容是字节级的,没有换行就可能判错。
  2. 格式空格问题。这个我在3.6里说过了,前导空格或者多空格都会导致WA。
  3. 数据类型溢出。比如计算加法时两个10^9级别的int相加,直接超过int范围(约21亿),结果就错了。这个问题的排查标志是你算小数据全对,大数据结果就怪。
  4. 边界条件漏判。比如题目说n可能为0,你却没处理n=0的情况,这时候你的代码要么RE要么输出错误。
  5. 多组输入处理错误。把多组数据当成单组处理,大概率只能过第一个样例,后面的全部WA。

排查WA我习惯用的方法:先看输出格式里有没有多余空格和换行;再改成极端数据测试(比如n=1、全0、全最大数、无数据);最后检查所有整数变量有没有可能溢出的地方。

4.2 RE(Runtime Error):运行时错误

基础题阶段的RE基本都是数组越界和除零。

  • 数组越界的典型信号:你的数组大小恰好等于n,然后循环里从0到n遍历了一遍,越界了。防止方法是所有用到数组下标的循环,都写一遍边界检查,尤其注意最后一个下标是n-1。
  • 除零的典型信号:类型是RE而不是WA,因为程序在运行过程中崩溃了。检查代码里所以除法、取余的表达式,确认除数不可能为0。如果你实现的逻辑里count可能在特定输入下等于0,就要加防御判断。

4.3 TLE(Time Limit Exceeded):超时

基础题一般不会考算法复杂度,但如果你的代码超时了,往往是循环写坏了。最常见的情况:

  • 用了一个while(1)但没有在合适的时机break,导致死循环。这种代码在OJ上一定会TLE。
  • 用了递归却没有终止条件,或者递归深度过大导致栈溢出但显示TLE。
  • 多组输入统计方式低效。虽然基础题不卡这个,但养成习惯:能用O(n)解决的,别写O(n²)。

4.4 PE(Presentation Error):格式错误

PE通常不扣分但也不算AC。你只需要把输出的每个字符、空格、换行都和题目样例比对。特别容易出问题的位置是"最后一个元素后面有没有多余空格"和"每组输出之间有没有空行"。如果题目要求"每组输出后加一个空行",那最后一组之后也必须有空行;如果要求"每组输出之间用一个空行分隔",最后一组之后就不能有空行。这两种表述在OJ题面里经常出现,稍微读错就是PE。

4.5 CE(Compile Error):编译错误

CE是最好解决的,把编译器给你的报错信息复制到搜索引擎,通常能直接定位原因。基础题阶段常见的CE有:

  • 头文件缺失,比如用了scanf没包含<stdio.h>。
  • 变量名和系统关键词冲突,比如叫main的函数、叫printf的变量。
  • C++代码提交到了C语言的编译器上,或者C语言代码提交到了C++编译器但用了C++特有的语法。

5. OJ刷题习惯与长期价值的延伸思考

聊完了116、117、118这三道题的具体解法,我想额外说一些和"刷题"本身相关的心得。这批心得是在我刷完几百道OJ题之后才真正领悟的,如果能在100题阶段就意识到,后面的路会顺畅很多。

5.1 正确使用搜索引擎和题解

碰到不会做的题,去搜题解不丢人。但关键在于怎么搜、怎么用。

我的建议是:至少独立思考30分钟以上再去看题解。这30分钟里你可以画图、手推数据、写假代码。即使最后没做出来,你的大脑已经把题目的所有细节过了一遍,这时候再看题解,你能一眼看出"原来我卡在这个地方"。这种"卡住后的顿悟"比直接背题解深刻得多。

看题解时,重点看思路而不是代码。一道题的题解代码可能很短,但背后的推导过程才是核心。建议看完思路后,合上题解自己重新写一遍代码。能无参考写出来,才算真正掌握了。

5.2 建立自己的错题本

不是纸质的错题本,而是电子文档,记录每一道WA/RE题目的原因。格式很简单:

题目编号:116 知识点:多组输入 错误原因:没处理EOF,死循环 正确写法:while(scanf(...) != EOF)

坚持记录一段时间,你会发现自己的错误高度集中——有人老在数组越界上栽,有人老在格式化输出上翻车。集中攻克自己的高频错误,刷题效率能翻倍。

5.3 为什么基础题阶段要追求"一次AC"

很多人刷题追求的是"只要最终AC就行",过程中WA个七八次也无所谓。在我看来,基础题阶段一定要严格要求自己,尽量在第一次提交前就把代码检查到最完善的状态。原因很简单:

OJ题目的竞争不只存在于"做对"和"做错"之间,还存在于"花多久做对"上。到了真正的竞赛或面试场合,没有人给你无限次重来的机会。从基础题阶段就养成"提交前静态检查"的习惯——检查变量是否初始化、数组边界是否越界、输出是否换行、多组输入是否处理——这些看似琐碎,却能让你在关键时刻少犯低级错误。

5.4 从基础题到进阶题的自然过渡

当你刷完116、117、118这个批次的题目,你会发现脑子里的思维定式发生了微妙变化:读题时不再盯着"这题要调什么API",而是先想"输入怎么处理、边界在哪、输出格式是什么"。这个转变,恰恰是刷题水平进入下一阶段的标志。

下一个阶段你会开始接触排序、递归、贪心、动态规划这些真正的算法知识点。届时你会发现,无论算法多难,代码的骨架依然是:

读入数据 → 处理数据 → 输出结果

而你对这个骨架的熟练程度,正是在116、117、118这些基础题上一点一点磨出来的。

回头再看这三道题,它们就像学走路时的扶墙——你可能觉得扶着墙走不算什么本事,但正是这一步步的笨拙,才让你后来能够跑起来。如果你正卡在某个基础题上,别急,把题目拆开,把输入输出捋清楚,把边界条件写在纸上,然后一把梭,AC就是你的。

我个人现在偶尔还会翻回基础题区,但不是为了刷题量,而是为了找找那种"码代码本来就这么简单"的初心。当你被后面的难题虐到怀疑人生时,回来AC几道基础题,真的挺治愈的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询