
2月13号那天我做了件特别朴素的事打开OJ平台把基础题119、120、124三道题挨个刷完然后在笔记本上记了一笔。可能有人觉得三道基础题有什么好说的但如果你真的在OJ上被“Wrong Answer”卡到怀疑人生就会明白基础题才是最能暴露问题的地方。我自己的感受是刷OJ最怕的不是难题而是基础题里藏着的一堆隐性细节比如循环边界、取模顺序、字符串结尾符、多组输入的读法每一处都能让你从“本地跑得好好的”变成“提交就是过不去”。这篇记录就是围绕这三道题展开的但重点不是“给答案”而是分享我怎么读题、怎么写、怎么踩坑、怎么排查。无论你是刚开始接触OJ刷题的学生还是准备专升本、考研机试、编程竞赛入门的选手这套思路都能直接搬走用。不同平台的题号不一定通用但知识点和坑是通用的。1. 项目背景为什么我坚持刷“基础题”1.1 题号背后的题库逻辑先说清楚2.13指的是日期119、120、124是我刷的那个OJ题库里的题号不是什么全国统一编号。很多学校都有自己的OJ比如杭电OJ、郑州轻工业大学OJ、湘潭大学OJ、东方博宜OJ、杭师大OJ每个平台的题单和编号习惯都不一样。你在这个平台看到119可能是“求奇数和”换到另一个平台119可能就变成“核酸序列配对了”所以别纠结编号本身要看背后的题目类型。我刷的这个题库基础阶段通常把题目分成几条线循环与分支、数组与字符串、简单数论、函数与递归。119、120、124正好踩中了其中三条最关键的线循环累加、最大公约数、回文判断。别小看这三类题它们几乎是所有OJ入门题单的标配也是后面做模拟、搜索、动态规划时反复要用到的基本功。我特意选在同一天刷这三道题是想看看自己在“最基础的知识点”上还有没有盲区。结果还真让我抓到几个问题int溢出、取模和交换的位置、字符数组大小计算。这些坑如果不刷题光看书是根本记不住的。1.2 适合谁看、要解决什么问题这篇文章适合三类人。第一类刚注册OJ账号、还没通过几道题的新手。你看完这篇文章能知道一道基础题从读题到提交的完整流程是什么样的遇到WA、RE、TLE这些状态时该从哪里查起。第二类已经刷了几十道题但经常“本地能过、OJ过不了”的选手。你会在文章里看到很多我自己踩过的坑比如输入缓冲区里的换行符、输出行尾的空格、多组数据的EOF读取这些细节往往就是你们和AC之间的那一步。第三类准备各种机试和面试的人。华为OD机考、校招笔试很多单位用的就是OJ模式考的就是你在限时条件下能不能写出干净、正确、不超时的代码。基础题练不好后面全是空中楼阁。一句话基础题不值钱但基础题背后的思维值钱。2. 三道基础题的完整拆解2.1 第119题循环累加里的边界问题我刷到的119题题意大概是输入一个正整数n求1到n之间所有奇数的和。第一眼看上去这不就是“送分题”但送分题恰恰是最容易失分的题因为它的坑全在细节里。先说思路。最简单的做法是用for循环从1开始每次加2累加到sum里。代码如下#include stdio.h int main() { int n; scanf(%d, n); long long sum 0; for (int i 1; i n; i 2) { sum i; } printf(%lld\n, sum); return 0; }这段代码本身没问题但如果你只写到这一步很容易在边界条件上翻车。比如题目说“n是正整数”那么n最小是1循环至少要执行一次。如果你把循环条件写成i nn1时sum直接就是0但正确答案是1。这就是典型的边界bug本地测试你可能只试了n3和n7没试n1结果一提交就WA。还有数据范围的问题。很多入门题会告诉你n不超过10^9这时候sum的极限大约是2.5×10^17明显超出int的21亿上限所以必须用long long。我一开始就习惯用int第一次提交直接WA后来看了题目里的数据范围才反应过来。这个教训我一直记着读题先看范围写变量类型之前先估算最大值。再说循环步长的问题。奇数序列的步长是2但有人喜欢用if (i % 2 1)判断再累加这样写法也OK但每次循环多了一次取模操作。数据量小无所谓数据量大时还是步长2更干净。另外如果题目改成“求1到n之间所有偶数的和”初始值就变成2步长仍然是2思路完全一致。这种小变体可以顺手多练几遍。细节上还有一个点输出必须换行。题目的输出格式写的是“输出一个整数”但OJ比较输出时通常要求末尾有换行有些平台对最后的换行不敏感但为了保险printf里一定要带\n。别小看这个换行Presentation Error就是这么来的。2.2 第120题最大公约数与辗转相除法的“反直觉”120题是一道经典的基础数论题输入两个正整数a和b输出它们的最大公约数。这个题很多人第一反应是“从min(a,b)往下遍历找到一个能同时整除的数就break”这种暴力方法在小范围数据下勉强能过但一旦数字达到10^9级别基本就是TLE。更稳的解法是辗转相除法原理一句话两数的最大公约数等于较小数与两数余数的最大公约数。写成代码就是#include stdio.h int gcd(int a, int b) { while (b ! 0) { int temp a % b; a b; b temp; } return a; } int main() { int a, b; scanf(%d %d, a, b); printf(%d\n, gcd(a, b)); return 0; }这个代码里最“反直觉”的地方是你不用先判断a和b谁大。因为如果a小于b第一次取模时a % b的结果就是a本身然后循环里会把b赋给a把a赋给b其实自动完成了一次交换。所以不管输入顺序如何最终都能正确得到gcd。很多人第一次看到辗转相除法时会觉得“这怎么可能对”但你自己举几个例子比如gcd(12, 8)、gcd(9, 15)、gcd(7, 13)手算一遍就懂了。这里有一个容易算错的地方求最小公倍数时很多人会直接写a * b / gcd(a, b)。如果a和b都很大比如10^9和10^9乘积就是10^18可能溢出int甚至long long。正确的写法是先除后乘a / gcd(a, b) * b。这个细节在OJ题里经常被单独拿出来考别踩坑。还有一个我实际遇到的细节如果数据范围是int但输入值可能到2^31-1a * b就会溢出。用long long接收更保险。很多OJ平台的C语言编译器里int和long long的混用会有警告但不影响结果为了保险起见我一般把变量和返回值都声明成long long。2.3 第124题回文判断里的字符串细节124题是判断一个字符串是否为回文字符串也就是正着读和倒着读一样。比如“abcba”是回文“abcd”不是。这道题看着简单但字符串相关的坑特别多。标准解法是用双指针一个从左边往右走一个从右边往左走遇到不一样的字符就返回0如果两个指针相遇说明是回文。代码#include stdio.h #include string.h int isPalindrome(char *s) { int left 0; int right strlen(s) - 1; while (left right) { if (s[left] ! s[right]) { return 0; } left; right--; } return 1; } int main() { char str[1005]; scanf(%s, str); if (isPalindrome(str)) { printf(YES\n); } else { printf(NO\n); } return 0; }这个题最典型的坑有三个。第一个坑是strlen的返回值类型。strlen返回的是size_t在64位系统里是unsigned long long。如果你是先用int right strlen(s) - 1;而字符串长度是0空串结果会变成-1转换成很大的正数导致越界。基础题里字符串一般不会为空但你最好养成习惯先检查strlen(s) 0的情况。第二个坑是循环停止条件。有人喜欢写成while (left right)其实在字符串长度为偶数时left和right会交错多比较一次也无所谓但为了防止越界正确条件是left right。这个细节和二分查找里的条件判断很像基础题就是在帮你练这种敏感度。第三个坑是字符数组大小。很多题目说字符串长度不超过1000你开char str[1000]结果输入1000个字符时最后的\0没地方放直接栈溢出或编译错误。我一般习惯开char str[1005]或char str[10000]宁可多开一点也别卡边界。如果你想把这道题再升级可以试试“忽略大小写和空格”的回文判断。比如“A man a plan a canal Panama”算回文但需要先过滤空格和标点再做大小写归一化。基础题一般不会考这么复杂但用这道题练习字符串预处理后面处理真实数据时非常有用。3. 从读题到AC一套能直接复用的基础题实操流程3.1 读题阶段先抓“输入格式”和“输出格式”很多人做OJ题第一步就去看样例输入然后照着样例猜题意。这种做法能解决60%的简单题但一旦题目描述里藏了“多组输入”“输入以EOF结尾”“数据范围达到10^9”这些条件只看样例就会死得很惨。我的读题顺序是这样的第一遍通读全部第二遍只看输入和输出格式第三遍再回来看题目中间的描述。输入格式会明确告诉你几个数、什么类型、一行还是多行。输出格式会告诉你每个结果占一行还是用空格分隔还是需要保留小数点后几位。这些信息直接决定了你的scanf和printf写法。比如119题如果输入格式写的是“一个正整数n”那只需要一个scanf如果写的是“多个测试用例以0结束”那就要用while循环读到0为止。120题如果写“输入多组数据每组占一行”那你的主函数结构就要变成int a, b; while (scanf(%d %d, a, b) ! EOF) { printf(%d\n, gcd(a, b)); }这个while (scanf(...) ! EOF)是OJ入门阶段最重要的一个模式没有之一。很多从“普通编译器做题”转到OJ的人第一次就是死在这个地方本地IDE里没有输入了程序就自动停但OJ上是程序自己读不到输入才退出如果你不写while循环只读一组数据就return了OJ期待你处理完所有输入自然就是WA。3.2 编码阶段先写出“能跑”的版本再考虑优化我刷这三道题时有一个习惯先在本地IDE里写一个最直白的版本把题目的样例输入跑通然后再去想有没有更优的写法。比如120题我一开始就写了个暴力找约数的版本样例能过但我很清楚数据一大就会TLE于是再改成辗转相除法。这种“先对再优”的节奏对新手特别友好因为一开始就追求最优解很容易卡住。写代码时需要注意几点变量命名别乱来OJ题虽然不长但sum、gcd、isPalindrome这种名字你看得懂别人也看得懂。函数尽量独立比如gcd单独写一个函数这样如果题目要扩展成“求一组数的最大公约数”你直接复用。还有提前把return 0;写在主函数末尾这是很多OJ的编译环境所要求的虽然有的编译器不写也能过但写了更稳。调试时我习惯用“三组测试数据”验证自己的代码正常情况、边界情况、特殊输入。比如119题的边界是n1120题的边界是ab、a1、b1124题的空串和单字符。这些测试在本地IDE里加几行printf就能确认提交之前一定跑一遍。3.3 提交阶段理解OJ的判题状态到底在说什么提交之后你的代码会进入一个判题队列几秒钟后返回一个状态。第一次接触OJ的人看到“Wrong Answer”和“Runtime Error”往往一头雾水我把这几个状态整理成了下面这张表都是我实际刷题时遇到过的状态含义常见原因ACAccepted通过恭喜可以下一题了WAWrong Answer答案错误逻辑有误或输出格式不对PEPresentation Error输出格式错误多了或少了空格、换行RERuntime Error运行时错误数组越界、除以0、递归栈溢出TLETime Limit Exceeded超时算法复杂度过高CECompile Error编译错误语法错误、选错语言MLEMemory Limit Exceeded超内存数组开得太大WA是新手最常遇到的状态但它其实是一个“好消息”说明程序能跑到结束至少没有崩溃只是答案不对。排查方向主要有两个一是你拿题目的样例输入本地跑一遍通常样例能过但WA说明你没有覆盖到特殊边界二是看输出格式是不是多了个空格或者少了换行。PE最容易被当成WA其实很多OJ会把PE和WA分开但如果平台不区分你要主动检查输出行尾。RE里最普遍的原因是数组越界。比如124题里字符串长度是1000你只开了char str[1000]输入1000个字符时最后一个\0就会写到数组外面。TLE则是算法问题119题用循环是O(n)如果n是10^9C语言大概0.1秒能过换成O(n^2)的双重循环必挂。基础题阶段TLE出现次数不多但你必须知道它与算法复杂度直接相关。4. 常见问题与排查技巧实录4.1 本地能过、OJ上WA的三个经典原因我见过太多人包括我自己在本地IDE里测试全对一提交就WA原因高度集中在三个地方。第一个是scanf格式化字符串里的空白。很多人写scanf(%d\n, n);觉得加了\n更严谨实际上scanf会跳过输入流中的空白字符空格、换行、制表符你加不加\n通常没区别但如果输入里前后有空格多写的\n反而可能让你多读一个空行导致下一条数据读取错位。正确做法是不要加多余的空白符scanf(%d%d, a, b);中间用空格分隔即可。第二个是gets和scanf混用。如果前面用scanf(%d, n);读入了一个整数后面再用gets(str)读字符串你会发现str的第一个字符是空字符串因为输入缓冲区里还留着那个换行符。解决办法是在gets之前先用一个getchar()把换行吃掉或者直接改用scanf(%s, str)。OJ题目里的字符串一般不含空格所以用scanf(%s, str)是最省心的。第三个是for循环的边界条件。前面提到的119题里i n和i n结果完全不同。判断循环边界时我习惯先代入最小值和最大值各跑一遍在草稿纸上写两行结果再编译看起来慢实际上能省下大量WA排查时间。4.2 不同OJ平台的差异从杭电到郑轻、东方博宜国内OJ平台非常多杭电OJ的题目经典但判题严格郑州轻工业大学OJ对新手友好但有些题目描述有歧义东方博宜OJ有视频题解适合自学湘潭大学OJ和杭师大OJ也有自己的题库体系。不同平台的差异主要体现在题面风格、数据范围、判题状态类型上但核心做题思路完全一样。有一个最容易被忽略的差异是“用不用智能指针”。你在洛谷、力扣写惯了C的vector和智能指针到了学校OJ上如果只能用C语言模式很多代码就要重新用数组和动态分配来写。特别是像“西北农林科技大学oj智能指针”相关课设题那样的题目可能会明确要求你手写内存管理而不是用std::shared_ptr。所以刷基础题时我建议尽量用纯C语法把指针和数组基本功练扎实后面再切到C的STL会轻松很多。平台差异还体现在“是不是多组数据”上。有些平台默认所有题都是多组输入有些平台则明确说“只有一组输入”。我个人的习惯是只要题目没写“单组数据”我就一律用while (scanf(...) ! EOF)处理这样在哪个平台都不容易出事。但要注意如果题目确实只有一组数据这个while也只会执行一次结果不变。4.3 让我印象深刻的三个坑第一个坑是char数组越界导致RE。124题我一开始开char str[100]但题目要求长度不超过1000我输了一个999长度的字符串直接RE。这个错误特别低级但特别典型。后来我给自己立了个规矩字符数组统一开成“题目长度上限5”宁多勿少。第二个坑是PE。120题我多输出了一句“gcd is: ”结果就是AC变PE。OJ只会比较你的输出结果任何多余的提示文字都会被当成错误输出。这也是为什么所有OJ题解里都只写printf(%d\n, ...)从来不写“结果等于”这种提示语。第三个坑是long long的格式化占位符。在Windows的Visual Studio里long long打印要用%lld但在某些老版的OJ编译器里可能要写%I64d。现在大多数Online Judge都跑在Linux上用GCC所以%lld是标准答案。但如果你在本地Windows上用VS做题提交时最好把占位符统一改成%lld省得CE。4.4 一套简单的OJ排错口诀刷完这三道题后我把自己的排错流程总结成一句口诀先验输入再看输出然后查边界最后调复杂度。具体来说先验输入scanf的格式对吗变量类型对吗多组输入循环了吗再看输出每个输出都有换行吗有额外字符吗小数精度对吗然后查边界n1、ab、空串、最大范围这些值跑过吗最后调复杂度O(n)够不够要不要优化成O(log n)数组大小够不够这套流程看着简单但每次WA的时候都照着顺序走一遍基本能解决九成问题。你刷的题越多越会发现OJ不只是在考你会不会写代码更在考你会不会排查代码。我个人刷完2.13这三道题的最大体会是基础题从来都不是“简单题”而是“陷阱题”。119、120、124这三个编号在我这里已经变成了循环边界、辗转相除、字符串结尾符的代名词。刷题这件事今天多踩一个坑明天就少一次WA。如果你也正在刷OJ不妨把自己卡住的题的编号和错误状态记下来过两周再回头看你会看到自己真正的进步。