
作为一个早就该把OJ刷完、却一直拖到现在的人这两天总算把东华OJ的71到75题整理完了一遍。这批题在整套题库里不算难但正好卡在从“语法熟悉”到“会写一点小逻辑”的过渡段很多同学就是从这里开始分化的——写得顺的后面越刷越顺卡在这里的很容易弃坑。这篇就把我自己的思路和踩坑记录整理出来如果你也在刷东华OJ正好卡在71到75这个区间可以直接照着我这个路子走。先说清楚东华OJ不同年份、不同老师开的课题号顺序偶尔会微调所以如果你手里的71到75和我这版不完全一样也别慌。我的原则是只看知识点不背题号。这套题核心考的就是数组处理、字符串统计、矩阵操作、简单数学、排序。搞定这五样后面遇到大部分基础题都不虚。1. 这批题目到底在考什么1.1 从题目定位看能力分水岭东华OJ前70题基本是顺序结构、分支、循环、简单函数属于“照着例子写就能过”的阶段。从71题开始题目开始要求你自己设计数据怎么存、流程怎么组织、边界怎么处理难度提升更多体现在思维层面而不是语法层面。打个比方前70题就像教你认识螺丝刀、扳手、电钻每把工具单独演示一遍。到了71到75题开始要求你拿着这些工具组装一张桌子你得自己判断先拧哪颗螺丝、哪里需要加固、哪里留缝。代码量不大但每一步都得想清楚为什么。我在刷这批题时的体感很明确数组去重、字符串计数、矩阵转置、完数判断、字符串排序这五个点几乎每个都是后续题目里的“基础建材”。比如去重逻辑在后面的集合题、图论题里都会反复出现矩阵转置是二维数组处理的典型样板字符串排序则是字典序处理的第一课。如果这批题你只是把代码抄了一遍过掉后面一定会回来补课。1.2 适合谁来参考这篇记录如果你是刚学到数组和字符串、准备开始刷OJ的在校生这篇可以直接当操作手册用。每道题我都给了完整的C语言实现思路和代码但我不建议你直接复制交上去因为OJ系统查重是标配而且抄一遍对你没有任何帮助。如果你已经能独立AC一些题但总在边界条件、格式输出这些隐蔽坑里翻车这篇里的“问题排查”部分可能对你有用我专门整理了自己WA掉的那些原因。如果你是在准备面试、刷算法题的职场人这批题相对偏基础但恰好可以用来快速找回写代码的手感。我自己就是空窗了挺久后拿这套题热身一天刷完第二天写工作代码明显顺手很多。2. 刷题前先搞定这些准备2.1 编程语言和编译环境怎么选东华OJ对语言没有太多限制C、C、Java、Python都支持。但我个人建议如果不是老师强制要求首选C语言刷这套题。原因很朴素这套题考的就是C语言课程的核心知识点用C写能直接暴露你对指针、数组、字符串操作到底熟不熟。用Python的话很多题一两行就绕过去了爽是爽了但等于没练。编译环境我用的是本地的VS Code加MinGW-w64顺手配了C/C扩展。如果你还在用那种很老的IDE也别焦虑能编译能调试就行OJ不看这个。我主要看重本地调试方便断点一打数组内容直接看比printf大法舒服太多。本地写代码有一个好处可以自己造测试数据。OJ上答案错误只是给一个WA不会告诉你哪里错。在本地多验证几个边界场景能省掉好多次无意义的提交。2.2 输入输出格式这些“潜规则”东华OJ的判题规则和主流OJ基本一致但有几个点值得专门提醒都是我自己踩过坑后总结出来的。第一输出多组数据时要么每组之间用换行分隔要么按题目要求输出绝不能多打空格。很多同学喜欢在每一行结尾统一加空格这在OJ的字符串比对里几乎必WA。我的习惯是循环里第一个数单独输出后面的数先输出空格再输出数值这样行尾就不会有多余空格。第二有些题目要求多组输入直到EOF。这需要用while(scanf(%d, n) ! EOF)这种写法我第一次做的时候没处理直接只读一组数据交上去半天想不通为什么WA。后来才意识到题目描述里那句“多组测试数据”不是摆设。第三注意数组越界。C语言不检查越界越界了程序不一定会崩但可能在关键时候给你一个莫名其妙的输出。最安全的做法是数组定义时多留一点余量比如题目说最多100个元素你定义成105不丢人。2.3 本地调试的小技巧我调试时最常用的是printf大法和断点结合。printf大法适合快速确认逻辑走到哪一步了断点适合看某个变量在每一轮循环里的变化。还有一个我觉得很实用的小技巧写一个简单的数据生成器配合自己的代码做批量测试。比如写一个随机生成数组的小程序然后一遍遍地跑你的去重代码看结果是不是稳定。这个习惯看起来很原始但在刷题阶段能帮你培养对代码的“手感”。3. 第71到73题数组和矩阵的基础操作3.1 第71题数组去重我做的第71题是数组去重输入一个整数数组输出去重后的数组保持原有顺序。说来简单但里面有个小坑不能排序。因为题目要求保持原有顺序这意味着你不能先排序再相邻去重否则顺序就变了。思路不复杂双重循环外层循环遍历每个元素内层循环检查它在前面的元素里是否出现过没出现过就输出。时间复杂度是O(n^2)对于题目给的数据规模完全够用。如果你想优化可以开一个哈希表或者用布尔数组标记但按OJ的数据量来说没必要代码越简单越不容易出错。我当时第一次交就WA了原因是我在输出格式上犯了之前说的毛病每输出一个数就往后面加了个空格然后最后一个数又额外换行OJ比对时尾随空格直接判错。后来改成“第一个数直接输出后面的数先打空格再打数值”立刻AC。#include stdio.h int main() { int n, i, j; int a[1005]; int used[1005] {0}; // 标记当前元素是否已经输出过 while (scanf(%d, n) ! EOF) { for (i 0; i n; i) { scanf(%d, a[i]); } int first 1; // 控制输出格式 for (i 0; i n; i) { int repeat 0; for (j 0; j i; j) { if (a[j] a[i]) { repeat 1; break; } } if (!repeat) { if (!first) { printf( ); } printf(%d, a[i]); first 0; } } printf(\n); } return 0; }这题看起来简单但我觉得它值得认真对待的地方在于去重是很多复杂问题的子步骤。你不会写这个后面做并查集、做集合运算、做哈希相关的题目都会很吃力。3.2 第72题统计单词数量这题是字符串处理的经典题给定一行英文句子统计其中单词的数量。难点在于单词之间可能有多个空格句子首尾也可能有空格不能简单按“空格个数1”来算。我的做法是遍历整个字符串用一个标志位记录“当前是否正在一个单词里”。遇到字母或数字时如果之前不在单词里单词数加一然后标记为在单词里遇到空格时标记为不在单词里。这样无论连续多少个空格都只会触发一次计数。这里要注意输入方式。scanf(%s)遇到空格就停了读不了一整行。需要用到fgets或者gets。我在本地测试时用fgets因为gets在C11标准里已经被移除了在不支持旧标准的编译器上会报警告甚至报错。#include stdio.h #include string.h int main() { char s[1005]; while (fgets(s, sizeof(s), stdin)) { // fgets会读入换行符把它去掉 s[strcspn(s, \n)] \0; int count 0, in_word 0; for (int i 0; s[i] ! \0; i) { if (s[i] ! s[i] ! \t) { if (!in_word) { count; in_word 1; } } else { in_word 0; } } printf(%d\n, count); } return 0; }有个容易错的点多组数据输入时fgets会把上一行的换行符也读进来。我第一次没做strcspn处理每句话后面都跟了个换行符判断条件里没排除它导致单词统计结果永远多或少一个。这种问题在本地单组测试时完全看不出来一上多组数据就暴露。3.3 第73题矩阵转置第73题是矩阵转置常见版本是3乘3矩阵输入九个数输出转置后的矩阵。转置就是把行和列互换c[i][j] a[j][i]。这道题对新手来说真正的难点不是转置逻辑而是矩阵的排列格局。OJ要求输出成方阵形式每一行之间有换行同一行数字之间用空格分隔。我看到很多同学卡在这里逻辑对了格式错了格式对了换行多了。实际上你只需要在printf里控制一下每一行开始前不放空格行内数字用printf( %d, x)行尾换行基本就不会错。#include stdio.h int main() { int a[3][3], b[3][3]; int i, j; while (scanf(%d, a[0][0]) ! EOF) { for (i 0; i 3; i) { for (j 0; j 3; j) { if (i 0 j 0) continue; scanf(%d, a[i][j]); } } for (i 0; i 3; i) { for (j 0; j 3; j) { b[i][j] a[j][i]; } } for (i 0; i 3; i) { for (j 0; j 3; j) { if (j 0) printf( ); printf(%d, b[i][j]); } printf(\n); } } return 0; }矩阵转置本身是个超高频考点后面学二维数组、矩阵乘法甚至图像处理时都在用。我建议你把“双层循环控制二维数组遍历”这个模式彻底练熟做到一看见矩阵题代码条件反射就能写出来。4. 第74和75题从数学到排序4.1 第74题找完数第74题是完数判断。完数是那些“等于它所有真因子之和”的数比如6等于1加2加3所以6是完数。常见问法是找出1000以内的所有完数并按指定格式输出。这题的数学原理很简单但代码里藏着一个效率问题如果从1循环到n判断每个因数1000以内没问题但换到100000呢所以我在写的时候做了个小优化只循环到sqrt(n)每找到一个因数就同时把对应的那个大因数也加上。当然要注意平方数的特殊情况别把一个因数加两遍。#include stdio.h #include math.h int isPerfect(int n) { int sum 1; // 1一定是真因子 for (int i 2; i sqrt(n); i) { if (n % i 0) { sum i; if (i ! n / i) { sum n / i; } } } if (n 1) return 0; return sum n; } int main() { for (int i 2; i 1000; i) { if (isPerfect(i)) { printf(%d\n, i); } } return 0; }第一次写的时候我直接在主函数里做了两层循环代码又乱又容易错。后来把“判断完数”单独封装成函数主逻辑一下子清晰了。这也是我觉得刷OJ应该早点养成的习惯一个功能一个函数别把什么都堆在main里。后面题目的复杂度上来后这个习惯能救你很多次。4.2 第75题字符串排序第75题是排序相关常见版本是输入若干字符串按照字典序从小到大输出。字典序不是字符串长度而是逐字符比较和英文字典的编排方式一致。这题有两个实现维度一个是排序算法本身一个是字符串比较。C语言里字符串比较用strcmp交换字符串不能直接赋值要用strcpy到临时数组。如果你刚学到选择排序或冒泡排序用它们来排字符串是个很好的练习。如果已经学了qsort也可以直接用但要写比较函数这正好是锻炼函数指针的机会。#include stdio.h #include string.h void sortStrings(char s[][100], int n) { for (int i 0; i n - 1; i) { for (int j i 1; j n; j) { if (strcmp(s[i], s[j]) 0) { char tmp[100]; strcpy(tmp, s[i]); strcpy(s[i], s[j]); strcpy(s[j], tmp); } } } } int main() { int n; char s[105][100]; while (scanf(%d, n) ! EOF) { for (int i 0; i n; i) { scanf(%s, s[i]); } sortStrings(s, n); for (int i 0; i n; i) { printf(%s\n, s[i]); } } return 0; }这题我踩过一个有意思的坑用scanf(%s)读字符串时它确实能跳过空格但如果题目里要求读的是“含有空格的字符串”这里就会读错。你需要根据题意判断要不要换成fgets。另外交换字符串时临时数组的长度一定要足够大否则strcpy越界程序可能不报错但输出乱七八糟。5. 做题中踩过的坑和问题排查5.1 常见编译报错的解决办法刷OJ难免遇到编译报错。我见过最多的是expected ; before }这类纯粹是语法问题检查分号和大括号就行。还有一类是implicit declaration of function意思是你调用了没用头文件声明过的函数。比如用了strlen却忘了#include string.h用了sqrt忘了#include math.h。编译器一般会提示你照着提示补头文件就行。还有一个比较隐蔽的undefined reference to main。这个报错说明你主函数名字打错了或者代码里根本没有main函数。OJ判题时是单独编译提交的源文件如果main拼写不对整个程序没法运行。5.2 逻辑对了但WA怎么排查编译过了、本地跑样例也对一提交就WA这是最磨人的情况。我的经验是按顺序排查三件事。第一你是不是没处理多组数据。很多题明写或暗示“输入包含多组测试数据”你如果只处理一组样例可能碰巧能过但实际测点多组输入时必WA。解决办法就是统一用while(scanf(...) ! EOF)包一层。第二输出格式有没有和题目要求完全一致。这里的“一致”指的是不能有多余空格不能少换行也不能多换行。尤其注意最后一个输出项后到底要不要换行看题目怎么说。第三你的边界条件处理有没有问题。比如数组长度为0、输入的是负数、字符串为空、n等于1等等。这些极端情况往往藏在隐藏测试点里样例根本不会出现。5.3 用“造数据”的方式自查如果你反复WA但找不到原因我强烈建议自己造几个边界测试数据。比如数组去重那题造一个“所有元素都一样”的输入再造一个“所有元素都不一样”的输入再造一个“空数组”就能看出你的逻辑有没有漏洞。这个方法看起来原始但非常有效。造数据的过程其实就是逼自己重新读题、重新梳理思路的过程。很多时候WA的根因是漏看了一个限制条件比如数组元素的取值范围、字符串的最大长度这些信息在题目描述里都有造数据时会被迫回头再看一遍。5.4 关于抄题解这件事我不反对看题解但我反对直接抄。刷OJ的本意是练自己的代码能力不是刷通过率。如果一道题想了半小时以上还是完全没有思路你可以去看题解但看完之后要把代码关掉自己从头写一遍。写不出来的部分再回头看看完再关掉重写。这样反复几轮才是真正把思路吃透。我自己的习惯是每AC一道题就在本地注释里写下这题的核心思路和踩坑点。这样过两周回来翻一眼就能想起来这题在考什么、我当时卡在哪。这个习惯帮我省掉了大量重复踩坑的时间。6. 关于刷OJ这件事我自己的一些体会71到75题这批题目说实话难度不大但我在整理过程中依然感触挺深。很多看起来很基础的代码隔一段时间不写真的会手生。我这次刷题前有大概两个月没怎么写C一开始连fgets和scanf混用的边界问题都要想一下才反应过来。刷OJ的价值不在AC那一下的快感而在逼你去面对自己含糊的地方。数组越界你不查它迟早会在别的题里咬你一口scanf的换行符问题你不弄明白换个题目描述还是会在同样的地方栽跟头。把这些小坑挨个填平后面才会越走越顺。最后分享一个小建议刷题不要贪多每天两三道就足够了关键是每道题都吃透。你代码写完之后可以试试换一种实现方式比如把双重循环去重改成哈希标记把冒泡排序换成选择排序。同一个问题多写几遍不同写法比刷新题更练基本功。这套71到75题刷完你至少应该能做到看到数组题不慌、看到字符串题知道用什么读入、看到矩阵题手不抖。如果还有哪里不稳回头再刷一遍稳了再往后面的题走。