做OJ题最怕的不是遇到难题,而是遇到那种“思路明明没有问题,提交就是不通过”的题。上周做东华OJ U12--7~9这一组(全局题号28~30)时,我正好把这类感受完整经历了一遍。这三道题放在一起看,其实是在练三个基本功:读入与字符处理、递推与数据类型、结构体排序与比较函数。文章面向正在刷东华OJ、卡在“数组和结构体”单元作业的同学,也适合想把基础算法细节抠扎实的初学者。
我的建议是:如果你今天刚好准备做这三题,先别急着打开编辑器。把下面这篇复盘从头到尾看完,尤其是第4节的排查思路。看完之后你会少交很多次。
1. 题面拆解:这三题到底在考什么
1.1 先看懂任务编号,再动手查题
很多新手看到“U12--7~9”会先愣一下,其实这个编号只是作业系统里的章节标记。U12通常对应教材某一章,7~9是该章的练习题号,而东华OJ平台上的全局题号是28~30。我第一次做这组题时也找了半天入口,后来发现直接按全局题号搜索最方便,题目列表里打28就能跳过去。
这类编号的意义在于告诉你:这几题概念上是连贯的。也就是说,做完28题再去做29题,你会发现它们并不是完全孤立的知识点,而是从“单组数据处理”到“递推状态转移”再到“结构化数据管理”的渐进过程。如果你能意识到这种递进关系,做题时就不会只是机械地抄代码,而是会主动去想“上一题用的方法能不能迁移过来”。
1.2 28题习惯性考法:字符统计与哈希计数
我见过28题最典型的一种版本:输入一行可能包含空格的字符串,统计每个字符出现的次数,最后按ASCII码顺序输出。这类题不算难,但很考验你对“读入”和“循环范围”的敏感度。
字符统计的本质其实是一个简单的哈希表。把字符当作下标,把数组元素当作计数器,一次遍历就能完成。关键是要先想清楚统计范围是只统计字母,还是所有可见字符都要统计,以及空格、换行、大小写到底怎么处理。很多人写的时候没想清楚,提交后才发现题目要求“忽略空格”,而你的代码把空格也统计进去了。
另外,输出顺序也容易出问题。如果要求按ASCII码从小到大输出,你最好直接用一个长度为128的计数数组,而不是用嵌套循环反复比较字符串里的字符。后者时间复杂度高,而且边界容易错。用计数数组的话,输出时从小到大扫一遍数组,凡是计数大于0的就打印,天然有序。
1.3 29题习惯性考法:递推思想的第一次显形
29题在这个单元里,十有八九是递推。常见的方向包括:求斐波那契数列第n项、爬楼梯方案数、猴子吃桃剩余数量。这三种都能抽象成一个统一的模式——下一项由前一项或前两项计算得来。
看到“方案数”“第n项”“连续走台阶”这类字眼时,第一反应应该是能不能写递推式,而不是直接枚举。拿爬楼梯来说,到第n级台阶有两种来源:从第n-1级再走一级,或者从第n-2级再走两级,所以f(n) = f(n-1) + f(n-2)。这和斐波那契本质上是一个模型。
这一题的坑在数据范围。许多新手看见“第n项”就默认为int,结果n一大就开始溢出。比如斐波那契第46项是1836311903,还在int范围内,第47项就超过2147483647,直接变负数。提交后你不会看到“溢出”提示,只会看到Wrong Answer,然后陷入“为什么我代码没问题”的困惑里。
1.4 30题习惯性考法:结构体与多关键字排序
30题最常见的版本是:给n个学生的学号、姓名和成绩,按成绩从高到低排序,成绩相同再按学号从小到大排序,最后输出完整记录。这道题需要用到结构体和自定义比较函数,是很多初学者第一次接触“按两个字段排序”的场景。
这道题真正的难点不是结构体定义,而是“比较函数”你到底能不能写对。在C语言里用qsort,函数签名很绕,返回值符号的含义容易搞混;用C++的sort虽然简洁一些,但同样需要你理清“什么时候返回true、什么时候返回false”。
还有一个容易被忽略的知识点:排序的稳定性。标准库里的qsort和sort不一定稳定,也就是说,如果比较函数只写“成绩降序”,那同分学生的原始顺序不保证保留。所以在比较函数里必须把“成绩相同怎么办”也写清楚,这样不管算法内部怎么处理,最终结果都是确定的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手前必须想清楚的三个问题
2.1 输入格式决定你用什么读入函数
我观察到很多人在OJ上出错,不是逻辑问题,而是从读入开始就错了。这里把常见读入方式的基本差异列一下:
| 读入方式 | 遇到空格 | 遇到换行 | 末尾换行是否读入 | 适用场景 |
|---|---|---|---|---|
scanf("%s", s) |
停止 | 停止 | 不会 | 读取不含空格的单词 |
gets(s) |
继续 | 停止 | 不会 | 读取一行(已不建议使用) |
fgets(s, n, stdin) |
继续 | 停止 | 会 | 读取一行(安全) |
cin >> s |
停止 | 停止 | 不会 | 读取不含空格的单词 |
getline(cin, s) |
继续 | 停止 | 不会 | 读取一行(C++) |
从表格能看出,如果题目输入的一行里可能有空格,那么C语言下必须考虑fgets,C++下用getline。如果前面刚用scanf/cin读完一个整数,后面紧跟getline,记得先丢弃缓冲区里留下的换行符,否则你读到的是一行空串。这个话题在第4节还会再展开。
2.2 数据范围决定你用什么整数类型
很多新手会被“输出第n项”这类题误导,以为全部用int没问题。实际上,C语言里int通常只能表示约21亿。一旦中间结果超过这个值,它会绕回负数,最终结果自然错。
写递推题前,我的建议是第一时间看题面里会不会给出n的范围。如果没给,直接在代码里用long long。长期用long long不会超时,也不会有副作用,最多就是心里觉得“有点浪费”。但比起WA后反复调试,这点浪费完全值得。
如果题目要求结果取模,情况又复杂一点。你必须在每次加法后立即取模。比如f[i] = (f[i-1] + f[i-2]) % MOD,同时f[i-1]和f[i-2]本身已经取过模。这样能保证中间结果不会溢出。千万不要在循环结束之后才取模,那时候已经溢出成负数了,再取模也没有意义。
2.3 输出格式可能比结果更容易让你WA
OJ对输出格式的判定非常严格。多一个空格、少一个换行
