资讯详情

OJ刷题入门:字符串、数学与排序三题实战避坑指南

📅 2026/10/10 19:28:30 | 华诺云谱 👁 阅读
OJ刷题入门:字符串、数学与排序三题实战避坑指南
最近我在刷OJ平台正好做完了第19到第21题这三道连续的题。它们的难度都不算高但恰好覆盖了OJ入门阶段三个特别容易出问题的地方字符串处理、数学推导、数组排序。很多人刷OJ卡住的点其实根本不是题目本身有多难而是不知道在线判题系统是怎么工作的为什么会报格式错误为什么会超时为什么本地跑得好好的交上去就错。这三道题刚好能把这些问题全部踩一遍所以我打算把完整的做题过程和踩坑记录写下来分享给正在入门OJ刷题的朋友做个参考。先说明一下不同平台的题目编号不一样同样的“19-21题”在不同OJ上可能是完全不同的题。我这里写的是我个人刷题时遇到的三个典型题目统计字符种类、最大公约数和最小公倍数、数组排序。我觉得它们非常有代表性而且从易到难的顺序也符合大多数OJ前几十题的编排思路。如果你所在平台的题目和我写的不一样也建议把这个思路看成通用的解题框架而不是死记代码。1. 刷题前需要搞清的OJ底层逻辑1.1 在线判题系统到底怎么判定你的代码对不对在动手写代码之前一定要先理解OJ的判定机制。很多新手拿到题目就埋头写代码写完了交上去显示WAWrong Answer一头雾水排查半天也找不到原因。其实问题往往出在最基本的读写方式上。绝大多数OJ平台不会让你手动输入测试数据而是把输入重定向到一个测试文件你的程序通过标准输入stdin读取数据通过标准输出stdout写出结果。判题系统把你的输出和标准答案做逐字符对比任何多余的字符、少掉的换行都会直接判WA。我之前就遇到过这种情况答案明明是对的但多输出了一行提示文字比如“please input:”结果直接挂掉。这三道题恰好是入门阶段把输入输出规则体现得最清楚的题目因为它们都是经典的单样例、无文件读写的简单题非常适合用来理解OJ的运行模式。建议你在这三题上养成一个好习惯程序里绝对不要有任何“友好的提示语”每行输出严格按照题目给的要求来该有空格的不能少不能多该换行的一定要有换行。1.2 准备一个稳定的本地开发环境我刷OJ用的语言是C语言本地环境是VS Code加MinGW编译器。为什么不用Dev-C或者直接在网页上写虽然网页编辑器也能写但调试体验真的不一样。刷OJ必须养成本地调试、在线提交的习惯因为你不能总是直接在OJ上改代码那样调试一次浪费一次提交次数而且看不完整错误信息。本地环境的配置并不复杂但细节容易出错。我个人的建议是装一个支持C/C的现代编辑器比如VS Code搭配MinGW-w64编译器然后用命令行gcc编译运行。如果你只想用最简单的工具那用Code::Blocks也可以但至少要学会看编译错误信息在哪里这个问题很多人第一次做OJ的时候完全摸不着头脑。另外我强烈建议准备一个“万能提交模板”就是基本的头文件、main函数的固定写法。代码风格统一之后在OJ上遇到的编译问题是完全相同的以后做题只需要专注于题目逻辑本身而不是每次都在编译器报错上浪费时间。1.3 读题比写代码重要十倍做题时最忌讳一上来就写代码而要认真读题三遍以上。这三道题本身文字都不长但里面藏着几个容易忽略的字眼。比如“统计字符”这道题你要注意字符串里是否包含空格输入的结束标志是什么“最大公约数和最小公倍数”这道题要看清楚输入范围范围大就要考虑long long类型“排序”这道题要看清楚是从小到大还是从大到小要不要去除重复元素。我过去刷题最容易犯的毛病就是读完题目第一行就开始写。结果写完才发现题目要求多组数据而我默认只处理了一组。这三道题如果仔细把题目里的“输入格式”“输出格式”两个字段看透就已经成功了一半。2. 第19题字符统计与字符串处理2.1 题目原型长什么样我刷到的第19题是这样的输入一行字符统计其中英文字母、数字、空格和其他字符的个数。这道题在很多教材里都出现过是C语言教材课后题里的常客搬到OJ上就成了入门标配。它的输入条件一般是一行字符串可能包含空格以换行结束。输出要求是一行四个整数分别是英文字母数、数字数、空格数、其他字符数中间用空格分隔。看起来很简单但有一个隐藏问题怎么把带空格的一整行读进数组里。如果你用scanf(%s, str)来读字符串遇到空格就停了后面的内容全被截断统计结果一定不对。这题的考点其实就是这个能不能读入带空格的字符串。在C语言里可以用gets()或者更推荐用fgets()因为gets()在C11标准里被废弃了不少OJ平台会警告甚至拒绝编译。用fgets(str, sizeof(str), stdin)是最稳妥的方案。2.2 桶计数思路统计字符数量常见的思路叫桶计数本质上是用数组下标对应字符的ASCII码遇到一个数组对应位置加一。不过这道题只需要分类统计不需要统计到每个字符那么细定义四个计数器就够了。遍历整个字符串用if-else判断每个字符的类型写字母或者小写字母属于英文数字字符按字符判断也就是c 0 c 9空格字符就是c 剩下的全部归入其他类。这里要强调一个很多人会犯的错误就是把字符数字和数值数字混在一起判断。有人会写if (c 0 c 9)这在C语言里根本不对因为c是一个char存的是ASCII码值字符0的ASCII是48它大于9所以这个判断永远不会成立。判断数字字符必须写成c 0 c 9或者在字符常量上加单引号。2.3 参考实现与本地验证代码如下#include stdio.h #include string.h int main() { char str[1024]; int letters 0, digits 0, spaces 0, others 0; fgets(str, sizeof(str), stdin); for (int i 0; str[i] ! \0 str[i] ! \n; i) { if ((str[i] a str[i] z) || (str[i] A str[i] Z)) { letters; } else if (str[i] 0 str[i] 9) { digits; } else if (str[i] ) { spaces; } else { others; } } printf(%d %d %d %d\n, letters, digits, spaces, others); return 0; }本地测试的时候别只测一个样例要测边界。我建议至少测这几组纯字母字符串“abcXYZ”预期输出“6 0 0 0”。带数字和空格的字符串“a1 b2 c3”注意这里有两个连续空格预期输出是“3 3 2 0”。空行直接敲回车预期输出“0 0 0 0”。带标点符号的字符串“hello, world! 2024”数一数结果对不对。特别是连续空格的情况如果你用的读入函数不对根本读不进去答案必然错。2.4 代码里的细节陷阱这道题我当年踩过的坑有两个。第一个是fgets会把换行符也读进字符串里所以如果你不对\n做特殊处理就会把它统计进“其他字符”里导致最终结果比预期多一个。所以我很推荐在判断时加上一个条件str[i] ! \n直接跳过换行符。当然你也可以先算一下字符长度只遍历到len-1效果类似总之要记得把换行符挡在统计范围之外。第二个是数组长度问题。如果你把str定义成char str[100]而测试数据恰好超过100个字符fgets只会读入前99个字符加一个\0其他字符全部丢失统计自然不对。一般这类题不会给那么长的输入但留个1024的数组总没坏处我之前在别的题目里吃过数组开太小的亏提前养成长度取大一些的习惯特别重要。3. 第20题最大公约数和最小公倍数里的数学思维3.1 看清题目输出的计算顺序第20题是输入两个正整数m和n求它们的最大公约数和最小公倍数。这个题看着比字符统计复杂一点但核心数学原理很清晰一旦想通了代码反而比上一题还短。先说一个最核心的数学性质两个数的乘积等于它们的最大公约数记为gcd和最小公倍数记为lcm的乘积。也就是m * n gcd(m, n) * lcm(m, n)。所以只要计算出最大公约数最小公倍数直接用m * n / gcd就能得到。这里必须特别注意一个顺序问题如果你先用m乘n再除以gcd在数据较大的情况下可能会溢出。比如m和n都是10^9数量级的正整数相乘的结果是10^18超出了int能表示的范围。更稳妥的做法是先除后乘也就是写成m / gcd * n。不过这个前提是除法能整除根据数学定义它一定整除所以没有任何问题。3.2 辗转相除法的本质求最大公约数最经典的方法是欧几里得法也叫辗转相除法。它的原理不复杂gcd(a, b)等于gcd(b, a % b)不断把大数替换成小数和余数直到余数为零此时的另一个数就是最大公约数。用一个例子走一遍求gcd(48, 18)。 第一步48 % 18 12所以转换成求gcd(18, 12)。 第二步18 % 12 6转换成求gcd(12, 6)。 第三步12 % 6 0此时除数为6余数为0所以最大公约数就是6。 整个过程只需要三次取模运算。用循环实现特别简洁。3.3 代码实现与多数据输入的教训代码如下#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 m, n; while (scanf(%d %d, m, n) ! EOF) { int g gcd(m, n); long long lcm (long long)m / g * n; printf(%d %lld\n, g, lcm); } return 0; }这段代码里我加了一个while循环来控制多组输入这是因为不少OJ题会连续输入多组数据直到文件结束。判断条件用scanf的返回值不等于EOF即可也就是scanf(%d %d, m, n) ! EOF。有些新手习惯只输入一组数据并且不加循环平时测试没问题一旦遇到多组测试数据的用例就会只输出第一个结果后面全部丢失直接WA。另外注意我用了long long来接收最小公倍数的结果。按题目常见的输入范围m和n都可能是10^9级别即便我先除后乘lcm的结果也可能超过int的21亿上限所以必须用long long。printf的格式控制符也要对应写成%lld如果你用%d打印long long输出结果会是乱的。3.4 数学题的边界值测试本地测试时我建议比着边界条件测输入“6 8”最大公约数2最小公倍数24。输入“1 100”最大公约数1最小公倍数100。输入“100 1”看看顺序反过来是否也正确答案应该是1和100。输入两个相等的数比如“7 7”最大公约数7最小公倍数7。输入一个很大的数比如“1000000000 999999937”计算结果一定不能溢出。这道题特别适合检验你对数据类型的敏感度。入门阶段很多人觉得int就够了直到WA才发现是超int范围这就是不测试边界数据的代价。4. 第21题数组排序题的AC关键4.1 题目要求往往不止“排序”两个字第21题是排序题描述一般很短输入n个整数将它们按从小到大排序并输出。很多人一看“排序”就觉得实现过无数遍了秒写冒泡排序交上去然后得到一个TLETime Limit Exceeded运行超时。为什么超时因为很多OJ的排序题输入的n可以达到100000甚至1000000冒泡排序的时间复杂度是O(n^2)在n等于100000的时候就要执行约100亿次操作超时几乎是必然的。所以看到排序题第一步不是默写某个排序算法而是看数据范围。如果n不超过1000冒泡或者选择排序没问题如果n到10^5级别就需要快排、归并排序或者直接调用库函数。我刷到的这题n范围是10^5以下用快速排序勉强能过但更稳妥的方案是直接用C标准库的qsort函数。这是一个把复杂问题交给现成工具的典型案例。4.2 qsort的使用方法qsort是C语言标准库中的排序函数声明在stdlib.h里。它的参数有点绕新手第一用会觉得莫名其妙但用熟了以后会发现比自己手写快速排序要稳得多。qsort的函数原型是 void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *));一句话解释base是待排序数组的起始地址nitems是数组元素个数size是每个元素的字节大小compar是一个函数指针指向你自己写的比较函数。这个比较函数返回负数表示a排在b前返回正数表示a排在b后返回0表示两者相等。对int数组从小到大排序时比较函数可以这样写int cmp(const void *a, const void *b) { return (*(int *)a) - (*(int *)b); }注意强制类型转换这一步绝对不能省因为qsort是通用函数它不知道你传入的是int还是float还是结构体只能在比较函数里把void指针转成具体类型的指针再解引用取值。4.3 完整参考代码#include stdio.h #include stdlib.h int cmp(const void *a, const void *b) { return (*(int *)a) - (*(int *)b); } int main() { int n; while (scanf(%d, n) ! EOF) { int arr[100005]; for (int i 0; i n; i) { scanf(%d, arr[i]); } qsort(arr, n, sizeof(int), cmp); for (int i 0; i n; i) { if (i 0) { printf( ); } printf(%d, arr[i]); } printf(\n); } return 0; }这段代码有几个用心之处值得说明。第一是数组定义成100005留出5个单位的余量防止写代码时手滑越界。第二是输出格式OJ经常要求同一行的数字用空格分隔行尾没有多余空格。用if (i 0) printf( )可以很优雅地在数字中间加空格最后一个数字后面不会出现多余空格。第三是while(scanf(...) ! EOF)和上一题一样处理多组输入。4.4 排序题里最容易踩的三道坑第一道坑用了不稳定的“自定义冒泡”还自以为没问题。如果你老老实实写一个双重循环n稍微大一点TLE逃不掉。建议养成先看数据范围的习惯这一点特别重要。第二道坑比较函数写反了把从小到大排成了从大到小。这个错误特别隐蔽因为本地测试小数据时你可能眼睛一花没看出来。我的建议是自己写的时候多测几个乱序数据别只测一个正序样例。第三道坑忘记考虑数组里可能有重复元素。虽然排序本身不会让重复元素出错但如果你在代码里加了“去重”逻辑一旦写错就会导致输出数量不对进而WA。所以严谨的做法是题目要求去重才去重没要求就原样输出。5. 连续做完三题后的OJ避坑技巧5.1 报错类型自查清单OJ反馈的错误类型就那么几种含义完全不同对着诊断才能高效解bug。错误类型含义常见原因CECompile Error编译错误语法错误、头文件缺失、变量名拼写WAWrong Answer答案错误算法思路不对、边界没覆盖、输出格式问题TLETime Limit Exceeded)超出时间限制算法复杂度过高、死循环RERuntime Error运行时错误数组越界、除零、栈溢出PEPresentation Error格式错误输出多了空格、缺少换行ACAccepted通过恭喜不用改这三道题特别容易出现的错误是WA和PE而它们之间只差一个空格。比如排序题输出最后一个数字后面多了一个空格OPJ直接把整个结果和你对照发现不同就报WA或者PE。所以每次提交前按一下CtrlZ或重置输入重新跑一遍边界数据非常有必要。5.2 本地测试和OJ测试的区别本地运行正常并不代表OJ上能通过因为有三个关键因素不一样 第一本地用的编译器可能比较宽松而OJ用的编译器版本更严格比如gets()函数在部分OJ上直接无法通过编译。 第二本地只跑一组测试数据OJ可能跑5组甚至10组包括n1、n100000这样的极端数据。 第三本地的时间度量不准确OJ的CPU时间限制通常是1秒或2秒程序要在远比你电脑理论上更快更标准的环境里跑。所以超时问题必须在写代码之前就考虑清楚。我的习惯是每次提交之前先在本地用尽可能大的数据量压测一下。比如排序题生成10万个随机数测一测运行时间。如果明显卡顿就得考虑优化算法了。5.3 不同OJ平台的选择与注册注意点这几年我用过几家不同的OJ平台各有特点。杭电OJHDU Online Judge是经典老牌OJ题量非常大从入门到进阶都有很多算法竞赛选手都在上面刷过题。郑州轻工业大学OJ更偏向课程实验和基础入门题目难度相对友好适合大一学生刚开始接触编程时用。东方博宜OJ也是一款面向教学的平台题目更新快讨论区比较活跃。如果你是在校学生建议优先用学校指定的OJ平台因为老师布置作业以后题目编号和验收标准都是跟着平台走的。如果是为了备战竞赛那杭电OJ是绕不开的资源。不管选哪个平台注册后第一件事不是马上刷题而是花十分钟把平台自带的FAQ读一遍尤其是输入输出规则、外部文件导入方式、适合自己语言的编译器版本这些信息能让你后面少走很多弯路。5.4 做题节奏与提交策略连续做三道题之后我最大的感受是刷OJ要讲究节奏不要死磕一道题超过半小时。如果一道题卡了30分钟没有任何思路说明很可能有一个概念理解不到位这时候果断跳过下一道题或者去讨论区看看别人的提示。等做完了后面的题再回头想反而容易有新的灵感。提交策略上也有技巧。第一发提交之前自己先复查三样东西数组够不够大、数据类型够不够宽、输出格式是否严格匹配。很多WA其实都是这三样中的某一样导致的自己查出来比等OJ告诉你省事得多。另外不要在同一个错误上连续提交超过三次。如果连续三次都是WA停下手来重新读题亲自手算几个用例用笔推导一遍再改代码。闷头改代码效率很低我在第19题上就干过连续提交四次WA的蠢事后来发现是fgets多读了一个换行符白白浪费了三次提交。6. 从19到21题我总结的入门刷题心法做完这三道题一个很明显的感受是入门题目考的不是聪明的算法技巧而是基础功。第19题考你会不会处理字符串的边界第20题考你懂不懂整数溢出和数学公式的转换第21题考你知不知道选择合适复杂度的排序方法。这三者几乎是所有编程问题的共同基础。放在最后分享一个实战技巧每次AC一道题后马上写一份简短的笔记记录题目的陷阱、自己犯过的错误、以及提炼出的关键步骤。这个习惯一开始很麻烦但坚持几十道题之后你会发现自己对常见陷阱有了天然的敏感度。像第19题的换行符问题、第20题的long long问题、第21题的多组输入循环这些知识点如果不记下来过两个月换个OJ平台大概率还会再犯。我自己就是靠着这样的错题笔记从最开始一天只能过三道题慢慢进步到面对陌生题目也能快速定位考点。OJ刷题没有捷径但可以通过优化方法和记录经验让每一步都走得比别人稳。接下来我准备继续挑战后面的题库把二分、递归、结构体这些经典问题一个个吃透到时候再写一篇专题分享出来。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑