
我备考PAT乙级的时候有一道题让我印象特别深——1014. 福尔摩斯的约会。这道题在牛客网和PAT官网上都挂着“20分”的牌子看起来人畜无害实际上暗坑不少字符串处理稍微马虎一点轻则超时找不到错重则整个逻辑绕进去出不来。今天我就把这道PAT乙级真题从头到尾拆一遍不仅讲怎么做对还把做题时的思路断点和常见错法一并拿出来遛遛给正在刷题的朋友做个参考。这道题的核心是字符串匹配和时间格式转化考点集中在字符比较、ASCII码范围判断和输出格式控制。适合刚接触PAT乙级、正在刷字符串类型题目的考生也适合那些做题时总在细节上翻车的老手拿来查漏补缺。我会先讲清楚题目的逻辑结构再给出一份可直接复现的C语言代码最后把我实际调错过程中遇到的高频问题整理成速查表。1. 题目分析与考点拆解1.1 这道题到底在考什么很多第一次看到“福尔摩斯的约会”这个题名的人会以为是一道推理题实际上它跟福尔摩斯半点关系都没有纯粹是借用了一个故事背景。题目给了四行字符串前两行推日期后两行推具体时间最后输出一个类似THU 14:04的结果。听起来很简单但真正的考点并不在于遍历字符串本身而在于多个匹配条件的叠加与顺序控制。从PAT乙级的出题风格来看这种题属于“模拟题”大类核心考察三个能力遍历字符串时能不能准确区分“第一个匹配”和“第二个匹配”字符范围判断是否严谨比如星期几只认A到G小时只认0到9和A到N格式化输出是否规范小时和分钟必须补零到两位第二个值得说的点是这道题非常爱在“顺序”上做文章。题目要求先找第一对相同的大写字母再继续找第二对相同的字符而不是从开头同时找两对。这就意味着你需要用状态机式的思路去遍历用一个标志位记住当前已经找到了星期几再去处理小时或者干脆用两个独立的循环分阶段完成。1.2 为什么PAT乙级爱出这种题刷过PAT乙级的人应该都能感觉到字符串处理类题目占比很高。原因是这类题不需要复杂的算法基础但对细节的考察非常到位。比如这道题里的“第二对字符”如果不加注意很容易把第一次遇到的相同字符当作小时然后输出一个完全错误的时间而编译器还不会报错——它只会让你的答案在OJ上显示一片红。另外这道题的难度设置也有讲究。20分的题在PAT乙级中属于中等偏易的档次但通过率并不是特别高。主要原因就是“看起来简单实际容易漏条件”。只要输出格式少了一个零或者把分钟数算错一位整道题就白做了。这种题放在真实考场环境中其实是在考抗压能力和细致程度。我个人觉得做这种题最好的策略不是急着写代码而是先把题目里的每条规则在草稿纸上翻译成逻辑判断然后再动手。很多人一上来就写循环结果越写越乱。2. 规则逐字拆解把题目翻译成代码逻辑2.1 星期几的判定细节题目给的前两行字符串需要找到第一对相同的大写英文字母而且这个字母必须在A到G之间分别对应星期一到星期日。这里有几个容易踩的点。第一个点是“第一对相同”。注意是“对”也就是说两个字符串在同一个位置上的字符要相同而不是在一个字符串里找重复字符。我见过有人理解成在一个字符串中找第一个重复字母在另一个字符串中找对应关系那是完全不对的。第二个点是范围限制A到G。如果没有这个限制那么第一对相同的任意大写字母都行但题目明确要求A到G。所以在遍历时必须加上str1[i] A str1[i] G这样的判断。有些实现不加这个限制直接用if (isupper(str1[i]) str1[i] str2[i])这样当字符串前面恰好有一对相同的大写字母但不在A到G范围内时就会提前跳出循环导致答案错误。这里我当时的处理方式是单独写一个函数做映射把字符转成数字再转成星期字符串。比如A对应MONB对应TUE依次类推到G对应SUN。2.2 小时判定的顺序陷阱找到星期几之后注意是“继续”在同一对字符串中往后找而不是重新从开头找。所以这里需要一个状态切换用一个flag变量记录是否已经找到了星期的字符一旦找到后续再遇到的符合小时条件的字符才会被当作小时来解析。小时字符的范围是0到9和A到N。0到9映射到0点到9点A到N映射到10点到23点。注意这里没有O到Z所以判断条件要限制好。另外还有一个隐藏规则在找小时字符时不再要求是大写字母因为数字字符也是合法的。这一点很多教程没有强调但实际编码时容易漏掉导致把数字字符跳过输出错误。下面是我当时梳理的顺序逻辑遍历前两行字符串逐位比较如果还没有找到星期字符且当前两位字符相同且是大写字母且在A-G范围则记录星期并标记已找到如果已经找到星期字符继续往后比较遇到第一个相同且满足小时字符范围的字符则记录小时并停止这一个阶段的遍历这样就可以避免用复杂的状态机写法普通循环加一个布尔标记就能搞定。2.3 分钟判定的隐藏条件分钟部分由后两行字符串决定找的是第一对相同的英文字母不区分大小写然后取它们在字符串中的位置从0开始计数作为分钟数。这里的隐藏条件有三个必须是英文字母数字、空格、符号都不算字母不区分大小写但判定时需要用isalpha()函数或者手动判断a-z和A-Z位置是从0开始计数所以如果第一对相同字母出现在下标4的位置那分钟数就是04输出时要补零还要注意分钟部分是从后两行字符串的开头开始找不是接着前两行的位置继续。这道题的规则设计很容易让人惯性思维地往后延续但实际上前两行和后两行是两组完全独立的比较对象。3. 完整解题实现与逐步讲解3.1 C语言参考代码我把自己的实现贴出来。这段代码在PAT官网上测试过可以直接AC。为了读起来方便我把星期映射单独拎出来做数组这样比switch-case更清爽。#include stdio.h #include string.h #include ctype.h int main() { char week[7][4] {MON, TUE, WED, THU, FRI, SAT, SUN}; char s1[70], s2[70], s3[70], s4[70]; scanf(%s, s1); scanf(%s, s2); scanf(%s, s3); scanf(%s, s4); int len1 strlen(s1) strlen(s2) ? strlen(s1) : strlen(s2); int len2 strlen(s3) strlen(s4) ? strlen(s3) : strlen(s4); int i, flag 0; int day 0, hour 0, minute 0; for (i 0; i len1; i) { if (s1[i] s2[i]) { if (!flag) { if (s1[i] A s1[i] G) { day s1[i] - A; flag 1; } } else { if (s1[i] 0 s1[i] 9) { hour s1[i] - 0; break; } else if (s1[i] A s1[i] N) { hour s1[i] - A 10; break; } } } } for (i 0; i len2; i) { if (s3[i] s4[i] isalpha(s3[i])) { minute i; break; } } printf(%s %02d:%02d\n, week[day], hour, minute); return 0; }注意我用了scanf(%s)来读字符串这里有一个前提题目给的每行字符串不包含空格。如果一行里面有空格就必须改用gets或fgets。我在PAT官网和牛客网都提交过这两处题目的数据都没有空格所以scanf足够用。但你如果是在其他OJ或自己造测试数据就要留意这一点。3.2 关键代码行深度讲解if (!flag)这个逻辑是整个代码的灵魂所在。第一次进入且字符满足A-G时flag置1后续循环走到相同字符时由于flag已经是1就进入else分支去解析小时。这里很多人写错是因为他们用了一个continue语句想跳过结果跳过了本轮下一轮又回到了判断星期的分支导致状态错乱。小时判断的两个分支顺序也很讲究。我先把数字字符放在前面判断再把大写字母放在后面判断。为什么这样写因为数字字符的范围和小写字母的范围没有交集先判断哪个都行但如果有人用if (s1[i] 0 s1[i] N)这种写法就会出错——9的ASCII码是57A是65中间还隔着一些字符但9 A所以数字字符到9就结束了不会影响大写字母的判断。不过为了逻辑清晰还是分开写比较好。3.3 如何用其他语言实现类似逻辑如果你用的是Java思路完全一样只是把字符串读取方式换成Scanner.nextLine()把字符判断方法换成Character.isLetter()和Character.isUpperCase()。Python就更简单了因为有内置的isalpha()和isupper()方法甚至可以用ord()函数直接拿到ASCII码。不过Python有些实现容易掉进一个坑字符串是不可变的不能用下标直接修改但我们的需求只是读取和比较所以没问题。下面是一个Python风格的参考片段方便做横向对比week [MON, TUE, WED, THU, FRI, SAT, SUN] s1 input().strip() s2 input().strip() s3 input().strip() s4 input().strip() day hour flag False for a, b in zip(s1, s2): if not flag: if a b and A a G: day week[ord(a) - ord(A)] flag True else: if a b and (0 a 9): hour 0 a break elif a b and (A a N): hour str(ord(a) - ord(A) 10) break minute 0 for i, (a, b) in enumerate(zip(s3, s4)): if a b and a.isalpha(): minute i break print(f{day} {hour}:{minute:02d})有一点要注意Python中的zip会以较短的字符串长度为界所以不需要手动取min(len1, len2)这算是Python写法的一个小便利。但如果你是C语言思路转过来的建议还是显式计算长度这样不容易弄混边界。4. 调试实录我在提交时踩过的坑4.1 常见错误汇总速查表我把自己实际提交中遇到的高频错误整理成了表格这些错误在牛客网的讨论区和CSDN博客里也非常常见。如果你在OJ上反复提交不通过可以对照这个表逐一排查。错误现象根本原因解决方式输出结果中星期错误没有限制字母范围在A到G把其他大写字母当成星期添加s1[i] A s1[i] G判断小时结果总是10点以后忘记了数字字符0-9也是合法小时字符小时判断时增加0到9的映射分钟结果总是0没有判断字符必须是英文字母匹配到了数字或空格分钟判断时使用isalpha()函数或手动判断字母范围输出小时和分钟只有一位数格式化输出使用了%d而非%02d使用printf(%02d:%02d)读取字符串时出现乱码或超时输入行中包含空格时误用scanf(%s)改用fgets()或getline()读取一整行小时或分钟出现重复覆盖找到第一个匹配后没有及时break退出循环在hour匹配成功后立即break这张表值得你在提交前从头到尾过一遍。我敢说至少有一半人第一次AC不了就是因为表格里的某一行。4.2 两个容易忽视的逻辑盲区第一个盲区是“找小时字符时是否可以从已经匹配过星期的那一位开始继续”。答案是可以因为当flag变成1以后循环仍然从当前i的下一位继续执行并不会重新从头扫描。但如果你的实现是在找到星期后把索引重置为0那就会重复匹配前面对字符导致小时判断错乱。所以这里要特别注意千万不要重置索引。第二个盲区是“分钟的位置下标从0开始还是从1开始”。题目明确是从0开始但很多人在手算样例时习惯从1数导致结果差一位。比如样例中如果第一对相同字母出现在下标4那么分钟应该是04而不是05。这个只能靠细心没有别的技巧。另外还有一个做题习惯层面的问题在本地编译器上跑通并不代表OJ上一定正确。因为有些编译器对未初始化的变量会给默认值0而OJ上可能给随机值。我早期写代码时day、hour、minute有时候会忘记初始化本地运行恰好不报错提交上去却出现异常结果。所以无论使用哪个变量都建议先赋初值。5. 从真题出发这类题的通用解法与训练建议这道题做明白之后其实可以总结出一套处理PAT乙级字符串模拟题的通用套路。我以前认为刷题就是刷题刷多了才发现类似题目的解法骨架高度一致无非是“遍历 条件判断 格式化输出”。这类题目的通用步骤可以归纳为明确要匹配的字符范围和出现顺序用布尔标记或状态变量区分不同匹配阶段在匹配成功后立刻跳出或进入下一阶段避免覆盖注意输出格式中的前导零、大小写等细节用边界数据自测包括最小、最大、以及字符范围边缘如果把这个套路应用到同类题目上比如“旧键盘”“人口普查”“查验身份证”等你会发现很多题的解法是相通的。无非是旧键盘考集合查找人口普查考日期字符串比较而这道题考的是多阶段匹配。本质上都是对“边界条件”的敏感度考察。所以我的建议是做完这道题以后不要急着往下刷而是用它作为模板去练习同类字符串模拟题。在PAT乙级的题单里这类题大约占了三分之一的比重。把这类题吃透了你的分数下限就有了保障。我在实际做这道题时前后提交了4次才完全通过。第一次挂在星期范围没限制第二次挂在小时映射漏了数字第三次挂在分钟输出没有补零第四次才真正AC。说实话这种题目做错并不丢人丢人的是反复在同一个细节上失分。希望这篇文章能帮你少走几步弯路争取一把过。