ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OJ基础题刷坑指南:多组输入、格式化输出与数组边界处理

OJ基础题刷坑指南:多组输入、格式化输出与数组边界处理 记得三月初那会儿刚把OJOnline Judge的基础题刷到三位数正好卡在116、117、118这三道题上。说实在的这个阶段的题目不算难但特别有意思——它们不像后面的算法题那样需要绞尽脑汁想状态转移也不像前面的水题那样无脑输出Hello World而是刚好卡在你刚学会语法、还没形成算法思维的尴尬节点上。很多人在这个阶段刷题靠蒙看完答案觉得懂了换个题目又不会了。这其实就是典型的基础没打牢。我自己刷这三道题的时候感受最深的一点是基础题考的不是你会不会某个高深算法而是你对问题拆解的基本功。题目本身可能只是让你处理几个数字、排个序、反转个字符串但背后真正训练的是你读题、拆解、边界处理、还有调试的能力。这些能力看着不起眼但后来刷到一百多道题之后回头看真正让你AC速度拉开差距的恰恰就是这些底层东西。这篇文章我就拿116、117、118这三道基础题作为引子聊聊OJ基础题阶段该怎么刷、怎么想、怎么避坑。如果你也正巧刷到这个编号区间或者刚开始接触OJ没多久这篇文章应该能帮你少踩几个坑。1. OJ基础题阶段的核心逻辑题目编号背后的学习路径设计先聊聊这三道题所处的阶段。大多数OJ平台的前200道题编号并不是随机排列的而是根据难度和知识点的梯度来设计的。拿郑轻、杭电这类平台来说基础题区间一般是按照顺序结构 → 选择结构 → 循环结构 → 数组 → 字符串 → 函数 → 结构体这样的路径来铺设的每几道题集中训练一个知识点。116、117、118如果落在100~120这个区间通常是基础语法已经过完一轮之后的小进阶。这时候的题目特征很明显输入输出格式开始有了讲究。不再是无脑给几个数让你直接求和而是会有多组数据、带个数的多组数据这种常见套路。算法本身不难但讨厌细节。比如让你算个平均值、找最大值很多人卡住不是因为不会算而是因为不知道浮点数格式化输出的精度怎么控制。开始考察你读题能力。题面会稍微绕一点可能带个实际场景比如按成绩排序、统计身高你需要先把问题抽象成纯粹的计算任务。这个阶段最重要的认知是你刷的不是题是熟练度。基础语法你可能课堂上都听过但OJ题最狠的地方在于它逼着你自己动手把代码敲出来并接受机器的无情评判。语法课上你可能觉得这个我会了但真到了OJ上一个分号漏了、一个变量类型错了、一个数组范围开小了全部都会让你WAWrong Answer或者RERuntime 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头文件。如果要用Pythonprint(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个数然后逆序输出这些数。逆序输出的核心思路有两种全部存进数组然后从n-1下标往0输出。读入时不按顺序存第一个读入的数存到数组最后一个位置边读边倒着放最后顺序输出。第一种更直观也是大多数人的选择。但这个题目真正考察的其实是你对数组下标从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 从三道题提炼出的通用解题模板做完这三道题后我总结了一套适用于基础题的通用分析模板先确定输入结束条件是EOF、是特殊输入组合、还是先给了总数n。这个不确定就直接动手写十有八九要返工。再确定数据存储方式需要存数组吗还是边读边处理就行能用滚动变量解决的不额外开数组。写处理逻辑前先想边界只有一个数据时的行为、没有数据时的行为、最大值最小值时的行为。格式化输出前对着样例看一眼规则空格、换行、小数点位数、结尾是否有额外空格——这些OJ都是严格比对的。这套模板后来在我刷后面几百道题时反复用到强烈建议你也把它内化成自己的做题习惯。3. 完整实操记录从空白编辑器到AC的全过程光讲思路不够这一节我用一个和116/117/118难度相当的完整例子带你走一遍真实的实操流程。我们就拿多组输入求平均值保留两位小数直到输入0结束这道题来说。3.1 环境准备我用的本地环境是VS Code GCCWindows上用MinGWLinux/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你需要按CtrlZ再按回车Linux里是CtrlD。很多新手在本地测了半天发现程序卡住不动其实是终端没有发送EOF信号不是程序死循环。知道这个小坑能少浪费很多时间。3.5 提交并处理反馈本地测试通过后登录OJ平台提交。如果返回ACAccepted恭喜这道题过了。如果出现WA、PE之类的不要慌按后面的排错流程走一遍。我第一次提交这道题时其实是先拿到的PEPresentation 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; }这个例子想说明两件事数组开多大题面没说n的范围时你按常识开一个足够大的固定数组比如1005。如果范围明确是0 n 1000开1005就够如果明确是n 10^6就要开1000005同时留意栈空间。输出格式空格控制很多人喜欢在循环里每个数后面跟着输出一个空格最后输出一个退格符\b。这种做法在本地可能显示正常但在OJ的严格判定下容易出问题。更稳妥的做法是第一个数前不输出空格后面的数都在前面输出空格。4. 常见报错与坑点排查从PE到RE的心路历程OJ判题系统的返回结果对于新手就是天书看到WA第一反应就是我到底哪里错了。这一节我把基础题阶段最常见的几个报错整理出来附带排查思路。4.1 WAWrong Answer答案错误WA是最让人头疼的因为题目不告诉你哪个测试点错了。基础题WA的原因按出现频率排大概是没有加换行。很多新手写完输出就结束忽略了\n。OJ比对的内容是字节级的没有换行就可能判错。格式空格问题。这个我在3.6里说过了前导空格或者多空格都会导致WA。数据类型溢出。比如计算加法时两个10^9级别的int相加直接超过int范围约21亿结果就错了。这个问题的排查标志是你算小数据全对大数据结果就怪。边界条件漏判。比如题目说n可能为0你却没处理n0的情况这时候你的代码要么RE要么输出错误。多组输入处理错误。把多组数据当成单组处理大概率只能过第一个样例后面的全部WA。排查WA我习惯用的方法先看输出格式里有没有多余空格和换行再改成极端数据测试比如n1、全0、全最大数、无数据最后检查所有整数变量有没有可能溢出的地方。4.2 RERuntime Error运行时错误基础题阶段的RE基本都是数组越界和除零。数组越界的典型信号你的数组大小恰好等于n然后循环里从0到n遍历了一遍越界了。防止方法是所有用到数组下标的循环都写一遍边界检查尤其注意最后一个下标是n-1。除零的典型信号类型是RE而不是WA因为程序在运行过程中崩溃了。检查代码里所以除法、取余的表达式确认除数不可能为0。如果你实现的逻辑里count可能在特定输入下等于0就要加防御判断。4.3 TLETime Limit Exceeded超时基础题一般不会考算法复杂度但如果你的代码超时了往往是循环写坏了。最常见的情况用了一个while(1)但没有在合适的时机break导致死循环。这种代码在OJ上一定会TLE。用了递归却没有终止条件或者递归深度过大导致栈溢出但显示TLE。多组输入统计方式低效。虽然基础题不卡这个但养成习惯能用O(n)解决的别写O(n²)。4.4 PEPresentation Error格式错误PE通常不扣分但也不算AC。你只需要把输出的每个字符、空格、换行都和题目样例比对。特别容易出问题的位置是最后一个元素后面有没有多余空格和每组输出之间有没有空行。如果题目要求每组输出后加一个空行那最后一组之后也必须有空行如果要求每组输出之间用一个空行分隔最后一组之后就不能有空行。这两种表述在OJ题面里经常出现稍微读错就是PE。4.5 CECompile 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几道基础题真的挺治愈的。
返回列表