
东华OJ的题号从第7题到第9题正好卡在很多人从“会写代码”到“敢写代码”的过渡区。前6题多半是简单的输入输出和顺序结构到了7~9循环、数学、边界条件这些东西就开始进来了。我刷的时候最大的感受是题目本身不难难的是你根本没意识到自己哪里会错。这篇就把这三道题的核心思路、AC代码、还有我踩过的坑一次说清楚。不管你现在用的东华OJ题号和这里是否完全一致这一套“读题→拆解→写码→测边界”的方法放到任何入门OJ题上都通用。1. 拿到题目先别急着敲代码7~9题的整体思路拆解1.1 这三道题在OJ题目序列里的定位东华OJ的题目排序整体是循序渐进的7~9这个位置非常特殊。它既不是最开始的“Hello World”和“AB”那种纯热身题也不是后面动辄就要用数组、结构体、甚至递归的进阶题。它处在“你会用循环、懂基本数学运算、但还没接触复杂数据结构”的中间地带是几乎所有编程入门者第一次感受到“题目有坑”的地方。以我印象里最常见的版本来看这三道题分别是最大公约数与最小公倍数、数字反转、素数判断。它们有一个共同特点用嵌套循环、取模运算、数据类型边界来考察基础功力。你说它考算法吧谈不上你说它没难度吧一个数字反转就能让一批人在负数和前导零上翻车。所以我的建议是不要着急打开编译器就写。先用五分钟把题目里的输入范围、输出格式、特殊情况全部圈出来。这三道题考察的不是“你会不会写循环”而是“你能不能考虑周全”。1.2 读题时的几个关键信息点读题不是看故事是提取约束条件。以最大公约数那题为例题面里往往写着“输入两个正整数”这四个字就至少决定了三件事第一数据类型可以用int第二不需要考虑0和负数第三如果题目要求输出最小公倍数你要知道乘积可能会超范围。你看一句话就能推断出这么多信息这就是读题的意义。数字反转那题就更典型。题目会写“输入一个整数n”这个“整数”二字意味着可能是负数还需要考虑翻转后前导零的处理。有些版本还会明确n的范围不超过32位有符号整数的最大值这时候如果你用int存反转后的结果就可能溢出。判断素数的题会写“输入一个正整数n”这暗示n≥1那就要特判1不是素数。还有很多OJ要求输出的是小写“yes/no”而不是“YES/NO”这些细节在题面里都写得很清楚但新手经常瞄一眼就开始写最后挂在输出格式上。我的习惯是读题时拿支笔把“输入范围”“输出要求”“特殊情况”三行圈出来写完代码后逐条核对。2. 三道题逐个拆解题意、思路与可用代码2.1 第7题最大公约数与最小公倍数这道题的核心考点是辗转相除法。它的原理很简单对于两个数a和b它们的最大公约数等于b和a除以b的余数的最大公约数即gcd(a,b)gcd(b,a%b)直到余数为0此时的除数就是最大公约数。严谨的数学证明可以以后再补我用一个生活化的例子来帮你理解。你想求48和18的最大公约数用48除以18余12接下来求18和12的最大公约数18除以12余6再求12和6的最大公约数12除以6余0所以最大公约数是6。这个过程中问题规模在不断缩小但答案始终不变就像把一个大任务不断拆成更小的等价任务。代码写起来也非常直接#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); int g gcd(a, b); int l a / g * b; printf(%d %d\n, g, l); return 0; }注意最小公倍数那行的写法我用的是a / g * b而不是a * b / g。为什么因为a乘以b可能会超出int的表示范围哪怕题目给的两个数都不大。先把a除以gcd结果必然能整除再乘b这样中间结果会小很多。这个细节我吃了不少亏C语言里int溢出不会报错只会给你一个莫名其妙的负数。2.2 第8题数字反转数字反转这道题看起来就是把1234变成4321但实际上有三个隐藏考点负号、末尾零、溢出。比如输入-120正确输出应该是-21。因为负号保留120反转后又把末尾的0去掉。最简单的做法是数学法不断取末位再拼回去#include stdio.h int main() { long long n; scanf(%lld, n); long long sign 1; if (n 0) { sign -1; n -n; } long long rev 0; while (n 0) { rev rev * 10 n % 10; n / 10; } printf(%lld\n, rev * sign); return 0; }这里我直接用long long因为如果原题数据范围逼近int上限反转后可能刚好越界。用long long虽然不能解决所有溢出场景但能把出错的概率降到最低。注意n0的情况上面的循环一次都不会执行rev保持为0正好输出0不需要额外特判。如果题目允许用字符串处理也可以用字符数组反转。但数学法的好处是零额外空间、思路清晰、还能加深对“取余和整除”的理解我更推荐优先掌握。还有一类改版题会要求你判断反转后是否溢出那就需要你提前用原数字和上限做比较。具体做法是反转过程中如果rev已经大于(INT_MAX-n%10)/10说明下一步反转会溢出。这个判断式很多面试也会考但东华OJ第8题一般不会这么难懂long long就够了。2.3 第9题素数判断素数题是入门级的“数学题之王”也是后续很多数论算法的基础。常规思路是试除法从2到n-1逐个试除复杂度O(n)如果n是10^9级别的数肯定超时。所以必须优化到试到√n为什么可以只试到√n呢这要从因数的对称性说起。如果n有一个大于√n的因数a那么n必然能写成a乘b的形式而b一定小于√n。也就是说a和b是成对出现的只要在小于√n的范围内找不到因数那大于√n的方向也一定找不到。拿36举例因数是1、2、3、4、6、9、12、18、36√366在6这侧的因数如果我们都试过了没有那另一侧自然没有新的质因数。用这个结论试除次数从n降到了√n10^9开根号大约31623次OJ判题轻松秒过。实现上还有一个非常容易踩的坑判断条件如果用ii n当n接近int上限时ii会先溢出再比较结果是错的。稳妥的做法是写成i n/i或者先算出(int)sqrt(n)再循环。C代码我习惯写i n/i因为它既不需要调用sqrt库函数也不会溢出#include stdio.h int isPrime(int n) { if (n 2) return 0; if (n 2) return 1; if (n % 2 0) return 0; for (int i 3; i n / i; i 2) { if (n % i 0) return 0; } return 1; } int main() { int n; scanf(%d, n); if (isPrime(n)) printf(yes\n); else printf(no\n); return 0; }这段代码我顺手做了两个小优化先把2和偶数排除循环时从3开始每次加2直接砍掉一半的试除次数。别小看这点优化如果题目要求判断的素数个数很多这个差距立刻就能体现出来。更大的优化还有6k±1法进一步跳过能被3整除的数但作为入门题上面这个版本已经完全够了。3. 隐藏最深的往往是输入输出细节边界测试实录3.1 输出格式的“洁癖”要求OJ评测机本质上是个字符串比较器。你程序输出的内容必须和标准答案一个字符一个字符地完全一致。这就意味着多一个空格、少一个换行、输出了一句“please input”之类的提示语都会判错。我在这三道题上都犯过类似的毛病。最大公约数那题要求输出两个数中间隔一个空格我就习惯性地在printf里写了“gcd is%d, lcm is%d”结果在本地运行挺好一提交就是Wrong Answer。后来学乖了凡是涉及输出一律按照题面要求的格式逐字核对不添加任何多余字符。如果你的题目是多组数据输入还要注意读取方式。东华OJ很多题目用的都是“输入数据有多组每组占一行”这种描述那你的主循环就要写成while(scanf(%d, n) ! EOF)这种形式。很多人只会处理一组数据提交后只过了一道样例另一种常见错误是无限循环因为忘记在循环末尾更新变量。3.2 边界数据自测清单写完代码先别急着提交拿边界用例过一遍能拦住八成以上的WA。我把这三道题常见的边界数据整理成了一张表你可以直接照着测题目边界输入期望输出容易错的地方最大公约数1 11 1循环条件没写好导致死循环最大公约数2 10000000002 1000000000中间乘积/相加溢出数字反转00while循环一次不执行结果应为0数字反转-120-21负号保留且末尾0被丢弃数字反转10000000001反转后前导0全部消失素数判断1no特判n2素数判断2yes2是最小且唯一的偶素数素数判断2147483647yesi*i可能溢出导致死循环/误判这张表你可以保存下来。我后来带学弟学妹刷题时发现他们最大的问题不是不会写算法而是完全想不到要去测那些“看起来奇怪但是合法”的输入。OJ可不管你的数据“正不正常”它只按题面来。3.3 本地测试的小技巧本地调试时我习惯把测试数据先存到一个input.txt文件里然后用重定向方式运行程序。在命令行里就是./a.out input.txt。这样反复调试验证会快很多不用每次都手动敲一堆测试数据。在这里我要特别提醒如果你想用freopen(input.txt,r,stdin)这种方式来做本地测试提交前一定要把这一行注释掉或者删掉否则OJ评测时读取不到你本地这个文件直接一组数据都测不了结果就是Runtime Error或者Output Limit Exceeded。这个坑坑过无数人包括我。4. 常见报错与排查技巧实录4.1 编译错误看一眼第一行提示信息新手最怕看到Compile Error其实CE是所有错误里最好修的因为OJ会直接告诉你出错的行号和原因。最常见的有三类一是头文件缺失比如用了sqrt却没加math.h二是main函数写成了void mainC标准要求返回int三是函数名冲突。关于函数名我再多说一句。如果你用C写并且using namespace std那么gcd这个单词其实是标准库里的函数名在C17之前就有一些编译器扩展支持你自己定义int gcd(int, int)在个别编译环境下可能和std::gcd产生二义性。为了保险我自定义函数时习惯起名my_gcd或者干脆写独立的实现不使用#include bits/stdc.h里的同名冲突。如果你体的编译器比较老那更没必要给自己添堵。4.2 Wrong Answer先把样例跑通再考虑反例WA是出现频率最高的评测结果。遇到WA我的步骤是先检查是不是输出格式不对再看是不是数据范围溢出最后才怀疑算法本身。这三道题里最大公约数那题溢出问题很常见数字反转那题负数处理很常见素数判断那题漏掉1很常见。还有一个细节如果题目描述里写的“每组测试数据”而不是“一行数据”通常意味着有多组输入。这时候如果代码只处理了一组提交后的表现就是样例能过但一测评就WA一半。识别方法很简单看题目样例输入里是否有多行数据或者看题面是否出现“多组”字样。多组输入的标准模板是int a, b; while (scanf(%d %d, a, b) ! EOF) { // 处理每一组数据 }这里的EOF是End of File的缩写意思是读到文件末尾才停止。OJ在判题时会把你程序的输入重定向到一个数据文件评测程序读完所有数据后你的循环就能自然结束。很多人第一次见到这个写法觉得玄学其实它就是在说“读到没有数据为止”。4.3 Time Limit Exceeded排查循环和死循环TLE在这三道简单题里其实不常见但要真遇上了大概率是死循环或者平方根判断写错。举个例子判断素数时如果写for(int i2;iin;i)在n很大时ii溢出变成负数循环条件永远满足就会一直循环下去直到超时。排查TLE的笨办法是手动模拟几组小数据如果程序半天不出结果多半就是循环变量没更新。我还见过一个搞笑情况有人写反转数字时把n/10写成了n%10结果循环跳不出去。这种低级错误别看不上你越紧张的时候越容易犯所以遇到TLE先放空两分钟重新看一遍循环体。4.4 用printf大法帮自己“看”代码调试代码最直接的手段在OJ场景里就是printf大法。在关键位置打几个printf把中间变量打出来你就知道程序实际执行到哪一步了。比如数字反转那题在循环里打印rev和n当前的值马上就能看出来是自己漏了负数的情况还是漏了取余顺序。但提交前一定要把调试输出全部删干净。我自己吃过这个亏代码逻辑完全正确结果忘了删调试printf多输出了一堆中间值白白吃了好几个WA。后来我学了个技巧调试输出统一写成printf(dbg:...)提交前全局搜索“dbg”这个关键词一搜一个准再也不怕漏删。5. 从这三道题里练出的通吃能力第7题让你学会数学原理和代码实现的有效转化第8题逼你考虑负数和前导零这些边界情况第9题教你用数学知识降低算法复杂度。这三样东西恰恰是所有算法题的底层能力。后来我去刷更复杂的题目遇到问题时的第一反应不再是“这题我不会”而是“这道题有哪些边界条件需要注意”“这个循环最多会跑多少次”“中间结果会不会溢出”——这三个问题全部是从这几道入门题里练出来的。我自己刷题还有一个“三遍法”第一遍自己写第二遍看别人代码找更好的写法第三遍隔一周再重写一遍。你会发现隔一周之后你已经记不清答案了但当时踩坑总结出的“边界意识”还在这时候重写通常比第一遍快很多而且能写出更干净的代码。这个方法对入门到进阶阶段的提升特别明显。另外建议你把这三道题的常用模板整理到本地一个文件里比如辗转相除的gcd函数、反转数字的核心循环、素数判断的优化版试除法。后期刷题你会反复用到它们每次重新敲一遍浪费时间而且容易敲错。把模板改成自己顺手的风格存好C语言的就存.cC的就存.cpp以后直接复制粘贴改改参数效率翻倍。这三道题可以说是东华OJ对你“代码习惯”的第一次教育。刷完它们你手里的东西其实比几道AC代码值钱多了。后面无论再遇到什么题记住多问自己一句边界你考虑了吗溢出你管了吗输出格式你检查了吗答案都是“是”的时候再点提交不迟。