新闻详情

新闻详情

首页 / 资讯中心 / 详情

C语言刷题复盘:经典题目与避坑指南

发布时间:2026/10/2 3:39:05来源:尧图网络
C语言刷题复盘:经典题目与避坑指南
刷C语言题目最容易踩的坑不是题目做不出来而是做完就扔根本不复盘。题目敲完了、测试用例过了、提交AC了任务好像就结束了——结果过两周碰到同类型题照样原封不动掉进同一个坑里。我在PTA、PAT和浙大翁恺的练习题上折腾了大半年回头整理时才发现真正让我有收获的恰恰是那些来回改过好几遍的经典题而不是一遍过的送分题。这篇就做一次题目篇大复盘把几道样例性很强的C语言题目摊开来讲5×5鞍点问题、日期计算、字符串逆序、九九乘法表、完数还有PAT乙级1037的霍格沃茨找零钱。每道题我都会把解题思路、代码实现、以及做题时最容易忽视的细节全部过一遍顺便把while/do-while、scanf、指针、静态内存这些基础考点串进去帮你把刷过的题真正变成自己的东西。1. 5×5鞍点问题二维数组题最容易翻车的三个细节1.1 题目到底在考什么鞍点问题几乎是每个学C语言的人都会碰到的二维数组经典题。题目描述很简单给定一个5×5的矩阵找到一个位置使得这个位置的元素在它所在的行里最小同时在它所在的列里最大。满足这个条件的点就叫鞍点把它的坐标和数值输出来如果不存在输出未找到。你可能会觉得这不就是先找行最小、再判断列最大吗确实核心思路就这两步但实际写代码时翻车点非常多。我先我贴一版比较稳健的写法#include stdio.h int main() { int a[5][5]; int i, j, k; int found 0; for (i 0; i 5; i) { for (j 0; j 5; j) { scanf(%d, a[i][j]); } } for (i 0; i 5; i) { int min a[i][0]; for (j 1; j 5; j) { if (a[i][j] min) { min a[i][j]; } } for (j 0; j 5; j) { if (a[i][j] ! min) { continue; } int is_saddle 1; for (k 0; k 5; k) { if (a[k][j] min) { is_saddle 0; break; } } if (is_saddle) { printf(鞍点坐标: (%d, %d), 值: %d\n, i, j, min); found 1; } } } if (!found) { printf(未找到鞍点\n); } return 0; }1.2 同一行出现多个相同最小值很多人就挂了第一版代码我估计大多数人写出来是这样先在内层循环里找出行最小值的下标min_j然后去判断这一列是不是列最大。这个思路本身没错但它默认了一个前提这一行的最小值只有一个。只要题目数据里出现一行有两个相同的最小值这种情况直接只判断第一个 min_j 就可能漏掉真正的鞍点。你想想如果第2行的最小值是5而且这一行有两个位置都是5第一个位置的列里恰好有一个比5大的数第二个位置的列里没有——那真正的鞍点就是第二个位置但只记录第一个下标的写法永远找不到它。这就是为什么我在上面的代码里分成了两轮循环第一轮先求出这一行的最小值min第二轮再遍历这一行的每个位置凡是等于min的都去检查它所在列是否满足没有比它更大的数这样就不会漏掉任何一个候选点。还有一种变体题会把鞍点定义成行最大、列最小甚至两种定义混在一起出题注意看清题目要求。判断条件换成a[k][j] max就可以了基本上就是把比较符号反过来逻辑骨架完全一样。1.3 从热搜里看到的limits.h其实是在帮你兜底很多人搜这道题时会发现答案里带了#include limits.h用INT_MAX来初始化最小值变量。这种做法在某些场景下很有必要——比如你要在一组数据里找最小但不知道数据的取值范围为了保险起见把最小值初始化为INT_MAX这样第一个元素一定会比它小。不过在这个5×5矩阵的题里直接用a[i][0]初始化最小值其实更稳妥因为矩阵元素是明确存在的不需要用极端值来兜底。如果哪次我遇到有符号整数、求最大/最小、且数据类型可能覆盖到边界值这类题才会考虑用limits.h里的INT_MIN或INT_MAX。这道题另外一个隐蔽的翻车点是输出语句里用全角括号、中文冒号导致WAWrong Answer。C语言判题系统对输出的匹配是逐字符的标点符号差一点都不行。我见过有人代码逻辑没问题仅仅因为printf(鞍点坐标:(%d,%d),值:%d\n, ...)和题目要求的不一致被卡半个小网。2. 日期计算题从switch硬算到查表法的思路转变2.1 一个朴素但容易写错的解法第二道经典题是输入年、月、日计算这一天是该年的第几天。题目样子可以参考浙大C语言基础练习题通常输入格式是2024 3 5或者2024-3-5输出是65因为2024年是闰年1月31天 2月29天 3月5天 65。很多人第一反应是用switch写月份累加#include stdio.h int is_leap(int year) { return (year % 4 0 year % 100 ! 0) || (year % 400 0); } int main() { int year, month, day; int sum 0; scanf(%d-%d-%d, year, month, day); switch (month - 1) { case 11: sum 30; case 10: sum 31; case 9: sum 30; case 8: sum 31; case 7: sum 31; case 6: sum 30; case 5: sum 31; case 4: sum 30; case 3: sum 31; case 2: sum 28; case 1: sum 31; } if (is_leap(year) month 2) { sum 1; } sum day; printf(%d\n, sum); return 0; }这段代码的坑在哪switch的穿透fall-through机制用得很隐晦。case 11加完30天后如果没有break它会继续往case 10穿透把前面几个月的天数全部累加进去。这要求你写case的时候月份必须从大到小倒着写一旦顺序写错结果就全乱了。而且这段代码读起来非常费劲别人来看你代码时根本理解不了你在干嘛。2.2 查表法是怎么做到又短又不容易错的同样的逻辑用数组查表就清爽得多#include stdio.h int is_leap(int year) { return (year % 4 0 year % 100 ! 0) || (year % 400 0); } int main() { int year, month, day; int days[] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int sum 0; int i; scanf(%d-%d-%d, year, month, day); if (is_leap(year)) { days[1] 29; } for (i 0; i month - 1; i) { sum days[i]; } sum day; printf(%d\n, sum); return 0; }days数组的下标0对应1月days[1]对应2月。如果是闰年先把2月的天数改成29然后从1月一直累加到month - 1月因为数组下标从0开始所以循环到month - 1不包含month - 1本身最后加上当月日期。这个方法的核心思想是用数据代替代码逻辑。switch版本里每一月的天数都藏在case结构里而查表法直接把天数作为数据放进数组代码的每个循环、每个判断都一眼能看懂也不存在switch穿透的问题。我后来刷题的时候凡是遇到一堆固定数值要做判断的题目第一优先考虑的都是查表。2.3 闰年判断的边界条件闰年这个知识点很多教材里都写了四年一闰、百年不闰、四百年再闰但写成代码时依然有人漏掉year % 400 0这个条件。完整的判断是能被4整除但不能被100整除或者能被400整除。注意能够被400整除的年份本身就能被100整除比如2000年所以两个条件必须用||连接而不能只写year % 4 0。这道题还有一个输入格式问题。scanf(%d-%d-%d, year, month, day)能直接处理2024-3-5这种带横杠的输入它会按格式匹配横杠字符。如果你写成scanf(%d%d%d, ...)则要求输入里不能有横杠否则会把-3-5这部分当作下一个数来转换导致解析错误。做题前先把题目给的样例输入格式看清楚这能省掉很多无意义的调试时间。3. 字符串逆序数组、指针、递归三条路线的取舍3.1 字符串输入这一步已经淘汰了一半人字符串逆序是PTA上非常高频的基础题比如输入一个字符串将其逆序输出。很多人在输入环节就出了岔子用scanf(%s, s)读字符串结果输入hello world时只能读进来hello因为%s遇到空白字符就会停下来。如果题目明确说字符串中可能包含空格就必须用fgets或gets来读取整行。但gets在C11标准里已经被移除了编译时会有warning甚至报错所以我建议直接使用fgets#include stdio.h #include string.h int main() { char s[100]; fgets(s, sizeof(s), stdin); s[strcspn(s, \n)] \0; int len strlen(s); for (int i len - 1; i 0; i--) { printf(%c, s[i]); } printf(\n); return 0; }fgets会把换行符\n也一起读进字符串里所以输出前必须把末尾的换行去掉。这里用strcspn(s, \n)找到换行符的位置然后把这个位置上的字符替换成字符串结束符\0。这是我从刷题中总结出的一个固定搭配凡是fgets读入的字符串第一件事就是处理换行符否则逆序输出时末尾会多出一个空行被判格式错误。3.2 指针和双指针到底在优化什么如果你只是逆序输出直接for (i len - 1; i 0; i--)就行了。但很多题目的要求是将字符串原地逆序也就是不能借助额外数组要把s本身的字符顺序倒过来。这个时候双指针法是标准答案#include stdio.h #include string.h void reverse(char *s) { int i 0; int j strlen(s) - 1; while (i j) { char tmp s[i]; s[i] s[j]; s[j] tmp; i; j--; } } int main() { char s[100]; fgets(s, sizeof(s), stdin); s[strcspn(s, \n)] \0; reverse(s); printf(%s\n, s); return 0; }双指针的精髓在于一个指针从头往后走一个指针从尾往前走相遇时停止。每交换一对字符实际上就是把字符串的中心对称位置调换一次。这个思路在后面的链表反转、数组旋转里也会经常用到属于那种一道题学会十道题受用的套路。如果题目进一步限制只能用指针不能下标访问那代码里的s[i]要替换成*(s i)s[j]替换成*(s j)。数组名在表达式里会退化成指向首元素的指针所以s[i]本质就是*(s i)。能看懂这一层C语言指针的基础才算合格。很多人卡在指针这块不是语法不懂而是不知道数组名即指针、下标就是偏移量这个等价关系。3.3 段错误定位gdb的简单用法字符串逆序这类题还有一个常见问题——数组越界导致段错误。比如我在牛客网上见过有人写char s[10]; scanf(%s, s); // 错误多了一个scanf的%s本来就需要字符数组首地址加上就变成了指向数组的指针类型不匹配轻则warning重则直接段错误。另一个经典问题是strlen(s)使用前没有检查s是否以\0结尾如果fgets或strcpy使用不当导致字符串没有结束符strlen会一直往后读到内存越界程序直接崩溃。遇到段错误怎么排查我习惯用gdb。编译的时候加-g参数gcc -g reverse.c -o reverse gdb ./reverse进gdb后输入r运行程序程序崩溃时输入btbacktrace查看调用栈它会把崩在第几行、函数调用链全部列出来。比起在代码里到处加printf猜位置这个方法定位数组越界快得多。4. 九九乘法表和while/do-while循环基础里的隐形考点4.1 九九乘法表格式控制是隐藏送命题九九乘法表应该是C语言初学者最早接触的嵌套循环题但你真的敢说每次都能一遍写对吗题目要求输出1*11 1*22 2*24 1*33 2*36 3*39 ...标准的写法是#include stdio.h int main() { for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%-2d , j, i, i * j); } printf(\n); } return 0; }外层循环控制行内层循环控制列。j i保证了每行只输出到对角线。真正容易栽的是输出格式%-2d表示左对齐、占2个字符宽度。如果写成%2d右对齐整个乘法表的右边会多出一堆空格题目如果要求每项之间固定空格数量你就等着被Presentation Error教做人吧。还有一种题目要求每一行末尾不能有空格。上面的写法在i 9时每行末尾都带了一个空格个别严格的判题系统会判错。处理办法是把空格从每个表达式后面改成除了第一个表达式以外每个表达式前面加空格for (int j 1; j i; j) { if (j 1) printf( ); printf(%d*%d%-2d, j, i, i * j); }这个先判断后输出的思路在后面的链表遍历、数组打印中会反复用到值得记下来。4.2 while和do-while真的只差一个执行顺序吗九九乘法表用的是for但面试和考试里经常专门拷问while和do-while的区别。最本质的差异就是while先判断条件再执行循环体循环体可能一次都不执行do-while先执行一次循环体再判断条件所以循环体至少执行一次。特性whiledo-while判断时机先判断后执行先执行后判断最小执行次数0次1次典型场景遍历未知长度的输入、条件不成立时不应进入菜单循环、密码输入、必须执行一次再判断一个非常典型的应用是密码输入int password; do { printf(请输入密码: ); scanf(%d, password); } while (password ! 123456);这里用do-while是天然合适的因为不管密码对不对你至少要让人家输入一次。如果换成while你得先给password赋一个错误值来保证第一次能进循环代码反而别扭。还有一个容易被忽略的细节while和do-while后面如果紧跟;会造成死循环或逻辑错误。比如while (i 10); { i; }while (i 10);因为分号的存在循环体变成了空语句程序会卡在这里死循环。这种错误在代码review时不容易一眼发现排查起来极其费劲。我的建议是写循环体时要么统一带大括号要么养成不用分号的习惯。5. 完数和霍格沃茨找零钱数学建模与边界处理5.1 完数因子求和里藏着的时间复杂度思想完数这个名词乍一听有点吓人其实定义很简单一个数如果恰好等于它的真因子之和这个数就叫完数。所谓真因子就是除了它本身以外的所有正因子。比如6的因子有1、2、3、6去掉6本身1236所以6是完数。1000以内的完数只有三个6、28、496。最朴素的写法是从1到n-1枚举所有数判断能否整除并累加。但如果在更大的范围内找完数比如10000这个写法的复杂度是O(n²)会明显变慢。优化思路是如果i是n的因子那么n / i也一定是n的因子所以只需要枚举到sqrt(n)#include stdio.h int main() { int n, sum; for (n 2; n 1000; n) { sum 1; for (int i 2; i * i n; i) { if (n % i 0) { sum i; if (i ! n / i) { sum n / i; } } } if (sum n) { printf(%d , n); } } printf(\n); return 0; }这里有个小坑要注意sum初始值设为1而不是0。因为1是任何大于1的整数的因子如果从0开始累加后面判断时6就会变成167找不到。同时注意去重判断if (i ! n / i)否则当n是完全平方数时比如36因子6会被重复加两次。这道题真正的价值在于让你体会到把循环范围从n缩小到sqrt(n)时间开销直接降了一个数量级。后面学到判断素数分解质因数时这个优化思路会一直沿用。5.2 霍格沃茨找零钱混合进制转换的标准姿势PAT乙级1037题大致意思是哈利·波特在魔法世界的货币系统是29个Knut纳特等于1个Sickle银西可17个Sickle等于1个Galleon加隆。输入形如10.16.27表示10加隆16银西可27纳特要求计算两笔钱的差值并输出。这道题如果直接按三位分别做减法要处理借位比如纳特不够减要向前借1个银西可银西可又不够要向前借1个加隆逻辑很容易乱。更干净的做法是全部换算成最小单位Knut加减完再换算回去#include stdio.h int main() { int pg, ps, pk, ag, as_, ak; scanf(%d.%d.%d %d.%d.%d, pg, ps, pk, ag, as_, ak); int p_total pg * 17 * 29 ps * 29 pk; int a_total ag * 17 * 29 as_ * 29 ak; int diff a_total - p_total; if (diff 0) { printf(-); diff -diff; } int g diff / (17 * 29); int s (diff / 29) % 17; int k diff % 29; printf(%d.%d.%d\n, g, s, k); return 0; }这个统一到最小单位再换算的思路其实和日期计算题如出一辙——日期计算是统一到天货币题是统一到Knut。做题多了你会发现C语言考来考去本质上就是考你能不能把现实世界的换算关系抽象成简洁的数学运算。如果你上来就在加隆、银西可、纳特三位变量之间搞借位代码写十几行还容易错统一单位后三行搞定边界条件负数也只要简单判断一下。5.3 变量命名里的小教训上面代码里我用了as_而不是as是为了避免和汇编语言的语义混淆也为了阅读时一眼能看出它代表Sickle。更关键的是C语言里变量名不能和关键字冲突比如int as;在有些编译器环境下会有意想不到的问题。我见过初学者用int char 10;这种命名直接编译报错也有用int main 0;在局部变量里藏main函数名的虽然不至于报错但非常误导。变量起名这件事在刷题时不显眼但等你写几百行的大作业或者项目时命名混乱的代价会非常惨痛。6. 复盘的方法从一道题抽象出一类C语言考点6.1 把这些题目放在一起看考点其实很集中如果你把前面几道题放在一张表里横向对比会发现C语言基础题的考点高度集中题目核心语法隐藏考点5×5鞍点二维数组、嵌套循环极值查找、标志变量、多个相等值的处理日期计算数组、条件判断查表思想、闰年边界字符串逆序指针、字符数组输入缓冲区、\0、双指针九九乘法表for嵌套循环输出格式、左对齐while/do-while循环控制最少执行次数、死循环预防完数循环、取余因子对数优化、完全平方去重霍格沃茨找零钱整数运算、条件分支单位换算、负数处理看到没有翻来覆去就是数组、循环、指针、字符串、输入输出格式这几座大山。翁恺老师在浙大公开课里反复强调程序数据结构算法对应到C语言基础题上数据结构就是数组和结构体算法就是循环枚举和条件判断。当你把一套题目复盘完成发现自己能在新题上一眼看出这题是考查表这题是考双指针你的C语言基础就算真正扎实了。6.2 我自己的复盘四步法分享给你做完每道题之后我习惯按四步走第一步错题重写。提交失败过的题绝不去翻别人的答案先自己重新想一遍思路再手写一遍代码直到AC为止。这一步的目的是逼自己找出卡住的真实原因。第二步一题多解。能AC的题看看有没有别的解法。比如字符串逆序可以用下标、指针、递归三种方式实现日期计算可以用switch和查表法。每种解法都写一遍再比较它们的时间、空间和代码可读性。这一步能有效提升你把思路转化成代码的速度。第三步做考点标注。每道题在笔记本或者代码最上方注释里标注它考了哪些知识点比如鞍点二维数组极值逻辑与。一个月后回头翻你能清楚地看到自己在哪些考点上反复犯错然后针对性地刷同类题。第四步整理易错点清单。比如我自己的清单里就有scanf取地址符不能丢、fgets要处理换行符、printf格式要与题面完全一致、循环体不要加无谓分号。这些清单在每次大作业前过一遍很多低级错误直接就能避开。6.3 做题环境其实也会影响刷题效率最后说说环境。很多人用Dev-C或者在线C compiler做题功能太简陋调试全靠printf效率很低。我个人的建议是本地装一个完整的编译环境Windows下用VSCode加上C/C插件配置好gcc编译器Linux下比如Ubuntu虚拟机直接装build-essential就够了。VSCode的优势在于断点调试、变量监视都可视化比起gdb裸用对新手友好得多。配置VSCode的C/C环境其实不难核心就三件事装好gcc、配置tasks.json用于构建、配置launch.json用于调试。如果未来哪天闲下来我可以把当时的配置过程完整写一篇标题都想好了叫c语言之环境篇复盘。但在这之前先把题刷透环境再顺手也救不了不会做题的尴尬。刷题这件事从来不在于量而在于想。一道题做完了停下来问自己三个问题我用了什么数据结构这个结构为什么合适如果数据规模再大十倍我的代码还跑得动吗想明白这三个问题比闷头刷十道重复的题有用得多。我个人在复盘时还有一个体会把同一类题集中放在一起看比一道一道孤立地刷要高效得多。比如连续做十道带空格的字符串处理题你会发现它们的坑几乎一模一样——输入方式、换行符处理、长度边界。把这些共性的坑总结成自己的答题清单下次再见到同类题心里就完全有底了。如果你也在学C语言、刷PTA或PAT希望这篇复盘能帮你把这些经典题目真正吃透。下一轮我可能整理一篇更偏向指针和内存管理的题目复盘那部分才是C语言真正区分新手和老手的分水岭。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

用Frida-RPC实现安卓逆向:演唱会数据自动化获取实践 2026/10/2 4:31:23

用Frida-RPC实现安卓逆向:演唱会数据自动化获取实践

说实话,第一次把目标锁定在“安卓逆向 演唱会数据”这两个词上的时候,我脑子里蹦出来的第一个念头是:这东西能做成自动化吗?很多人一提逆向就想到破解、脱壳、分析so文件,一听就觉得门槛特别高。但真正把这些流程跑下…

阅读更多 →
Unity手游iOS Deep Link全链路实战:URL Scheme与Universal Links配置及参数传递 2026/10/2 4:31:17

Unity手游iOS Deep Link全链路实战:URL Scheme与Universal Links配置及参数传递

1. 为什么手游团队绕不开 Deep Link 这件事做过手游投放或者运营的兄弟应该都有体会:买量成本一年比一年高,用户点进来一次不容易,结果从浏览器点开广告、跳转到 App Store、下载、首次启动,这一整条链路上只要有一环断了&#xf…

阅读更多 →
AI+CAD落地实战:从Demo到工程化的技术难点与解决方案 2026/10/2 4:31:17

AI+CAD落地实战:从Demo到工程化的技术难点与解决方案

1. 为什么“AI CAD”的Demo看起来很美1.1 一个典型Demo的诞生过程先说说我见过最多的那类演示。打开一个网页,上传一张户型图或者机械零件的照片,点一下“生成CAD”,几秒钟后屏幕上出现一堆线条,看起来像模像样。再点一下“导出D…

阅读更多 →
速达财务SSTD3G服务器端部署全攻略:从解压到客户端连接 2026/10/2 4:30:57

速达财务SSTD3G服务器端部署全攻略:从解压到客户端连接

简介:速达财务SSTD3G_server_6.37.zip 是围绕速达财务SSTD3G系统推出的服务器端与客户端安装资源,适合中小企业财务部门及负责系统部署的IT人员使用。压缩包大小约316.81MB,已有404人学习。该安装资源覆盖服务器端与客户端两部分,…

阅读更多 →
PSO优化Kmeans聚类:居民用电行为分析实战 2026/10/2 4:30:57

PSO优化Kmeans聚类:居民用电行为分析实战

去年我接了个居民用电数据分析项目,核心目标很明确:根据智能电表采集的负荷曲线,把小区里几百户居民划分成几种典型的用电行为模式。我想着这不就是聚类嘛,直接Matlab里调个Kmeans函数分分钟搞定。结果第一版跑出来,轮…

阅读更多 →
Unity手游iOS Deep Link实战:URL Scheme与Universal Links配置及C#参数投递 2026/10/2 4:30:56

Unity手游iOS Deep Link实战:URL Scheme与Universal Links配置及C#参数投递

1. 为什么手游团队绕不开 Deep Link 这件事做过手游投放或者拉新活动的兄弟应该都有体会:买量买来的用户,点开广告之后如果只是被丢到 App Store 下载页,装完打开游戏却停在登录界面,那这个转化链路基本就废了一半。用户明明是被某…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉