
PAT乙级真题里1014《福尔摩斯的约会》是我见过最像“阅读理解”的一道编程题。这题分值20属于乙级卷面里中规中矩的题目但它几乎不用算法不碰数据结构唯一的考点就是字符串遍历加条件判断。可就是这道题每年都能放倒一片人而且翻车原因高度一致题面的三个条件没读全或者读全了没翻译成正确的代码。今天这篇就把这道20分题从头到尾拆开讲适合刚学完C语言、准备开刷真题的同学也适合在乙级卡在某个分数段想查漏补缺的老手。先澄清一个背景。网上搜“PAT”会出来一堆不相关内容芯片测试里有PAT工程地质里有地质图案库金融计算里还有PAT和PMT这些都跟程序员口中的PAT不是一回事。计算机考生说的PAT是浙江大学组织的程序设计能力测试Programming Ability Test分甲级和乙级乙级面向刚起步的同学重点考察C/C、Java、Python的基础语法和简单算法。这道1014就是乙级真题的常客题目主角是福尔摩斯所以大家都叫它“福尔摩斯的约会”。1. 题面是怎么回事把乱码还原成约会时间1.1 原题场景还原题目用大侦探收到一张奇怪字条作为包装字条上写着四段乱码看起来像这样3485djDkxh4hhGE 2984akDfkkkkggEdsb shgsfdk dHyscvnm福尔摩斯看了半天解码出约会时间是“星期四 14:04”。解码规则印在题面里一共三条输入总共给四行字符串前两行一组后两行一组。在前两个字符串中从左到右逐个检查相同位置的字符找出第一对相同的、且是大写英文字母的字符用来确定星期几接着继续往后找再找出第二对相同的字符用来确定小时。在后两个字符串中找出第一对相同位置且同为英文字母的字符那个位置的下标从0开始就是分钟。题目保证输入数据一定能够解出合法时间所以不用额外处理“解不出来”的情况专心做解码就行。这三行规则就是全部题眼后面的所有内容都在跟这三条规则死磕。很多同学一上来就写循环找“相同的字符”忽略了“大写”“字母”“第几对”“位置下标”这些限定词结果样例能过、一提交就错就是因为限定词没落实全。1.2 样例怎么推出来的把样例拿出来手动推一遍比背任何题解都有用。四行输入分别是3485djDkxh4hhGE 2984akDfkkkkggEdsb shgsfdk dHyscvnm先看前两个字符串。从左往右逐位对比位置2出现第一对相同字符都是‘8’但它不是大写字母跳过位置6出现第一对相同的大写字母‘D’。‘D’在字母表里排第4对应星期四所以星期部分是THU。确定星期之后继续往右看位置14出现第二对相同字符‘E’。“A”到“N”被规定为小时10到23E是A往后数第4个字母所以小时是14。再看后两个字符串位置1的‘’相同但不是字母跳过位置4出现第一对相同的英文字母‘s’下标是4所以分钟是04。最终输出THU 14:04。推完这个样例你会发现题目规则一点都不难难的是把这些规则一条条翻译成代码里的判断条件。这也是我说“阅读理解”的意思它考察的是你能不能把一段带故事包装的自然语言准确翻译成程序逻辑这才是PAT乙级真正想练的基本功。1.3 这道题真正在考什么从知识点上看这道题只涉及两件事字符串按位遍历、字符分类判断。前两个字符串需要你判断字符是不是大写字母、是不是数字、落在哪个区间后两个字符串需要你判断字符是不是英文字母。从能力上看它考的是“条件翻译”能力题面把星期、小时、分钟分别藏在三对字符里你要自己设计状态记录找了几对、找到了没有。这种题型在PAT乙级里非常典型后面好几道题也都是同一套路比如字符串类题目里经常出现的“旧键盘”“说反话”等基本功都是这一套。把这题吃透等于提前热身了好几个字符串题。2. 三个条件逐一拆解每一对字符各管什么2.1 第一对字符决定星期范围卡死在A到G先看最容易出错的第一对。规则原话是“第1对相同的大写英文字母大小写有区分”。这里有两个限定第一字符必须出现在两个字符串的相同位置第二字符必须是大写英文字母。但还有一个隐含限定容易被忽略那就是星期只有七天所以有效的大写字母只有A到G分别代表周一到周日。如果你找到的第一对相同大写字母是H或者Z它不构成星期信息必须继续往后找。很多第一次做这题的同学看到H和H相同就乐呵呵拿去算星期结果要么数组越界要么输出一个莫名其妙的结果。正确做法是先把相同位置且相同的大写字母判断出来再检查它是否落在A到G区间里两者都满足才算找到。映射关系就是简单的ASCII减法大写字母减A得到0到6的下标再用这个下标查星期缩写数组。A对应MONB对应TUEC对应WEDD对应THUE对应FRIF对应SATG对应SUN。注意缩写必须是三个字母THU不是THURSAT不是Sat输出必须严格按题目给的七种缩写来少了字母或者大小写不对都会判错。2.2 第二对字符决定小时数字和字母覆盖0到23找到星期之后继续从下一个位置往后找第二对相同的字符这个字符决定小时。小时的编码规则是字符0到9对应0到9点字符A到N对应10到23点。合起来刚好覆盖0到23这24个小时所以第二对字符的取值范围不是A到Z而是0到9和A到N这两段。这里有两个坑一个是在找第二对字符的时候你可能遇到的相同字符是普通小写字母、符号或者其他乱七八糟的字符这些都不算数必须满足范围条件才break另一个是范围判断要写对大写字母部分只到N不是Z漏掉N或者多算到Z都会出问题。小时映射的公式也顺手记一下数字字符用c减字符0得到0到9大写字母用c减A再加10得到10到23。这个“字符转数值”的操作在PAT里出现频率极高建议练成熟练工。另外还要记住查找第二对字符时不能回头必须从星期那对字符的下一个位置继续否则你把第一对字符再匹配一遍又会得到一个没意义的“小时”。2.3 第三对字符决定分钟取的是下标不是字符值分钟部分跟前两对不太一样它不关心字符是什么值只关心“第几对”里的“第几”。规则是在后两个字符串中找出第一对相同位置且同为英文字母的字符这个位置的下标从0开始就是分钟。注意三点第一这里说的是英文字母大小写都算但数字和符号不算第二分钟是下标不是字符本身很多人把s减字符0或者直接把s当数字用输出自然是错的第三下标从0开始如果第一对字母出现在开头分钟是00不是01。一个容易想歪的点是这里的“第一对英文字母”不需要管是不是大写只要两个字符相同并且都是字母就行。题目样例里第一对同位置相同字符其实是位置1的但它不是字母所以跳过最后落在位置4的s上。写代码时用isalpha()判断最省事记得包含ctype.h头文件。如果不想依赖库函数自己写判断也简单c a c z或者c A c Z两者取或就行。3. 代码怎么写从逻辑到可运行的实现3.1 动手前先想清楚两个小细节写代码之前先把两个容易卡住的小细节想明白。第一字符数组开多大。题目说每个字符串长度不超过60不含空格用scanf(%s)读入即可数组开成70绰绰有余。四行输入用四个char数组存后面三组交互完全独立先处理前两个再处理后两个。第二星期缩写怎么存。最省事的做法是准备一个字符串指针数组七个元素分别是MON到SUN找到星期后直接用字母减A当下标printf一次输出星期和一个空格时间部分最后再统一输出格式不会乱。关于保存状态查找星期和小时这个过程需要一个标志变量记录“星期是否已经找到”。找到了星期之后后面的相同字符才开始参与小时的判断。这个标志可以用int也可以直接借stdbool.h里的bool用int也完全没问题。状态设计的核心是让循环逻辑能区分当前是在找星期的阶段还是在找小时的阶段。这个区分想清楚代码结构就不会乱后面写起来一气呵成。3.2 完整C语言实现#include stdio.h #include ctype.h int main() { char s1[70], s2[70], s3[70], s4[70]; scanf(%s %s %s %s, s1, s2, s3, s4); char *week[7] {MON, TUE, WED, THU, FRI, SAT, SUN}; int hh 0, mm 0; int dayFound 0; for (int i 0; s1[i] s2[i]; i) { if (s1[i] ! s2[i]) { continue; } if (!dayFound) { if (s1[i] A s1[i] G) { printf(%s , week[s1[i] - A]); dayFound 1; } } else { if (s1[i] 0 s1[i] 9) { hh s1[i] - 0; break; } else if (s1[i] A s1[i] N) { hh s1[i] - A 10; break; } } } for (int i 0; s3[i] s4[i]; i) { if (s3[i] s4[i] isalpha(s3[i])) { mm i; break; } } printf(%02d:%02d\n, hh, mm); return 0; }这段代码用dayFound标志把找星期和找小时两个阶段放在同一个循环里。循环条件是s1[i] s2[i]意思是两个字符串都没到结尾只要有一个到结尾就停防止越界。字符相同是进入判断的前提不相同直接continue跳过。找到星期时先输出星期和后面的空格然后置dayFound为1之后每次遇到相同字符就进入小时判断分支命中0到9或A到N就记录小时并break。第三个循环同理遇到同位置相同且isalpha为真的字符时把下标i存成分钟break。这个版本逻辑紧凑唯一要注意的是在小时分支里如果遇到的相同字符既不是数字也不是A到N比如相同的小写字母或者符号程序会继续往下走不会break这完全符合题意因为那些字符不构成小时信息。3.3 另一种写法两个循环分开找更不容易看错如果觉得一个循环里塞两个阶段不够直观可以拆成两个for循环。第一个循环专门找星期找到后记录位置i并break第二个循环从i1开始找小时。这种写法的好处是阶段分明一个循环只干一件事判断条件不容易串。尤其对刚接触这类题的初学者来说先写两个循环把逻辑跑通再回头优化成单循环反而更稳妥。下面这个结构可以直接参考int i; for (i 0; s1[i] s2[i]; i) { if (s1[i] s2[i] s1[i] A s1[i] G) { printf(%s , week[s1[i] - A]); break; } } for (i; s1[i] s2[i]; i) { if (s1[i] s2[i]) { if (s1[i] 0 s1[i] 9) { hh s1[i] - 0; break; } else if (s1[i] A s1[i] N) { hh s1[i] - A 10; break; } } }注意第二个循环从i开始这个细节很重要。因为第一个循环结束时i还停在找到星期的那个位置必须让i先自增再判断下一个位置否则会把同一个字符当成小时再匹配一次。PAT考试时间紧张写代码最重要的是稳定选择自己最不会出错的写法才是正解。我个人建议初学者用双循环版本等熟练了再随意切换。3.4 附一个Python参考版本PAT乙级目前也支持Python提交Python写起来更短尤其适合时间不够或者想快速验证思路的时候。核心逻辑跟C版完全一致只是字符串不可变、用range遍历时长度要取两个字符串的较小值。这里给出一个参考实现week [MON, TUE, WED, THU, FRI, SAT, SUN] s1 input().strip() s2 input().strip() s3 input().strip() s4 input().strip() hh 0 day day_found False for i in range(min(len(s1), len(s2))): if s1[i] ! s2[i]: continue if not day_found: if A s1[i] G: day week[ord(s1[i]) - ord(A)] day_found True else: if 0 s1[i] 9: hh int(s1[i]) break elif A s1[i] N: hh ord(s1[i]) - ord(A) 10 break mm 0 for i in range(min(len(s3), len(s4))): if s3[i] s4[i] and s3[i].isalpha(): mm i break print(f{day} {hh:02d}:{mm:02d})Python的isalpha()会识别所有Unicode字母不过这组输入只会出现英文和符号使用没问题。如果对编码有洁癖也可以写成(a s3[i] z) or (A s3[i] Z)。提交前记得把print的格式控制好f-string里的:02d能自动补零比字符串拼接稳妥。Python的写法更适合赛后复盘或者快速验证用例考场上有把握的话用C更稳。4. 实战踩坑记录这些错误我全犯过4.1 最常见的翻车第一对字符的范围没卡到G这个错误我当年第一次做的时候就踩了。拿到题看到“第1对相同的大写英文字母”顺手写成s1[i] s2[i] s1[i] A s1[i] Z心想这就是大写字母啊样例也过了。为什么样例能过因为样例正好第一对合法大写字母就是D在A到Z范围内判断结果一样。但换一组测试数据就露馅如果前两个字符串在某个位置先出现相同的大写H再往后一点才出现合法的大写B正确答案应该是B对应的星期二而错误代码会因为H在A到Z范围内就直接break输出完全不对。这个bug在OJ上表现就是“部分正确”很多同学找半天都找不到原因。解决办法就一条星期判断的大写字母区间一定要写A到G不是A到Z。写完之后自己构造一组包含H的用例去跑一遍确认程序会跳过H继续往后找。这种“样例能过但提交部分正确”的题十有八九是条件范围比题面要求宽了。刷题时遇到这种情况第一反应不是怀疑评测机而是回头逐字读题检查每个范围判断。4.2 第二对字符小时范围判断漏掉数字0到9找小时的逻辑同样有范围问题。小时编码有两段数字0到9和字母A到N。有些同学记住了字母段却把数字段漏了或者反过来只判断数字。漏掉数字段的后果是当小时恰好是0到9之间的某个数字时程序找不到合法小时最终hh保持初值0输出一个错误时间。这个bug同样不会在样例里暴露因为样例的小时字母是E走的是字母分支。解决方式很直接两个判断分支写全一个判断0到9一个判断A到N顺序无所谓但一定要都写上。另外提醒一个细节小时分支必须在星期找到之后才生效。我见过有同学把两个判断写在一个if里结果第一对相同的大写字母A既是合法的星期又因为A落在A到N区间里被当成小时两段逻辑同时触发输出直接乱套。用两个循环分开写能免疫这个问题用单个循环务必记得星期标志位的判断顺序先判断星期是否就位再进入小时判断。4.3 第三对信息分钟是下标不是字符本身第三个高频错误是把分钟写成了字符值。比如找到相同的英文字母s有人会写mm s3[i] - 0或者直接mm s3[i]输出一个七八十甚至三位数。题目要的是“位置”也就是循环变量i不是字符的ASCII值。这个错误在样例里也很好认样例输出分钟是04如果你输出的是115之类那肯定把字符值当真了。修起来就一行mm i;别忘了下标从0开始计数的前提。顺带说一个更隐蔽的坑后两个字符串找的是“英文字母”不是“任意相同字符”。样例里位置1的两串相同但它不是字母必须跳过如果没加isalpha判断分钟就会错成01。我记得当年不少同学就卡在这还怀疑自己字符串读入出了问题。其实仔细读题就能发现题目用的词是“英文字母”而不是“字符”这两个词的差别恰恰就是考点。4.4 自测用例与调试技巧把这题AC之后建议保存一组自测用例以后遇到类似字符串题还能复用。核心思路是覆盖三类边界第一第一对相同大写字母不合法需要跳过第二小时是数字而不是字母第三分钟为00。我自己常用的几组用例写在下面你可以直接复制到评测系统里验证。输入字符串期望输出说明3485djDkxh4hhGE 2984akDfkkkkggEdsb shgsfdk dHyscvnmTHU 14:04原题样例HA0 HA0 a aMON 00:00第一对H不合法取第二对A作星期数字0作小时分钟在下标0A9 A9 b bMON 09:00小时是数字9检查数字分支ANc ANc 1x 1xMON 23:01小时N对应23分钟下标1且前面的1不是字母调试时有一个小技巧先把星期那行printf单独调试确认星期输出正确后再去调小时和分钟。因为三个时间要素层层依赖星期如果错了后面全错。用printf在关键位置打印临时变量比瞎猜快得多。我习惯在找到每一对字符的地方打印一下字符和下标记确认逻辑走到哪一步这样一次就能定位问题出现在哪个环节。5. 备考视角这道20分题怎么稳稳拿下5.1 难度定位属于必须全拿的保分题从乙级真题的难度分布看1014属于中规中矩的字符串题分值20一般出现在卷面中段。它不考算法思想只考基本功所以只要语法过关完全有理由拿满分。对目标是合格线的同学来说这类题是不能丢的对目标是高分的同学来说它更应该成为熟练工看到题面就能在五分钟内写完代码。我的建议是把这类“故事包装加条件翻译”的题单独归类刷题时优先保证它们全对再去碰需要动脑子的算法题。这跟考试策略也有关系先稳拿保分题心里就不慌后面做难题时心态会好很多。具体操作上拿到这道题先花一分钟把三个条件的字符范围写在草稿纸上再动手写循环。考试时最忌讳的是一边看题一边写代码写着写着把自己绕进去。把规则列清楚代码就是照抄逻辑而已。5.2 和翁恺课程、刷题网站的衔接很多走“浙大翁恺老师的C语言程序设计课加配套练习”路线的同学对这道题应该不陌生。翁恺老师在MOOC课程里一直强调动手写代码课程练习和PAT平台现在主要在Pintia上做题是打通的很多初学者就是刷完课后习题顺手开始刷PAT乙级真题。1014这道题作为乙级入门阶段的经典字符串题正好承接了课程里学过的字符处理、ASCII码、数组和格式化输出这些知识点。如果你还在跟课学到字符数组那一节之后就可以拿它练手了。这里也顺便说一句刷PAT乙级别贪多求快。我的经验是每天沉下心做两到三道做完看题解、记笔记、写自测用例比一天狂刷十几道然后全忘要有效得多。字符串题尤其适合反复做隔一周重写一遍看看能不能一次AC是检验自己有没有真正掌握的好办法。很多人刷题只追求数量AC完就跑过两周再看题目跟新的一样这样进步很慢。5.3 同类题型的扩展练习做完1014后面还有好几道同类型的字符串题值得趁热打铁。乙级里关于字符串处理的经典题还有“旧键盘”“说反话”“输出PATest”这些它们的基本功都是遍历、字符分类、计数和格式化输出考察点一脉相承。我的建议是做完1014之后按照“先看题面自己写一遍再看题解对比最后总结易漏点”的顺序把同类题一道一道过掉。遇到像1014这样条件比较多的题可以自己画一个简单的对照表把每个条件对应的字符串位置、字符范围、映射方式写清楚。例如福尔摩斯约会的对照表就是前两个字符串第一对相同大写A到G映射星期第二对相同0到9或A到N映射小时后两个字符串第一对相同字母的位置映射分钟。把这张表刻在脑子里下次遇到“给定规则从字符串里提取信息”的题直接套这个分析框架效率会高很多。最后分享一点个人体会。我第一次交这道题的时候错在把分钟当成字符值输出白白浪费了半个多小时最后靠printf打印中间变量才定位到问题。后来我养成了一个习惯凡是遇到“第几对”“位置”“下标”这种描述先在草稿纸上把规则列成表再动手写代码。像福尔摩斯约会这种题真正难的从来不是写代码而是把故事里的规则一句句拆明白。把这个习惯练好了你在PAT乙级里遇到的所有字符串题都会越做越顺手。