ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

蓝桥杯单词分析题详解:字典序处理与三种语言实现

蓝桥杯单词分析题详解:字典序处理与三种语言实现 写这篇笔记的时间是 2025 年 4 月 15 日我正好把蓝桥杯备赛题库里的单词分析题又完整复盘了一遍。这道题的题干很短输入一个只含小写英文字母的单词统计每个字母的出现次数输出出现次数最多的那个字母以及次数如果出现次数并列则输出字典序最小的字母。很多人第一次看到“字典序”三个字就发怵其实这个概念放到这道题里就是“字母表靠前的优先a 最小z 最大”。这篇文章不绕弯子直接讲清楚题目背后的考点、主流解法的对比、Python/C/Java 三版代码怎么写以及我在实际训练里踩过的那些坑希望能帮你把这类送分题稳稳拿满。1. 题目卡片与考点拆解1.1 原题长什么样蓝桥杯软件赛里单词分析是一道非常经典的入门级真题省赛和校内选拔都出现过类似描述题目原文大致如下小蓝正在学习一门神奇的语言这门语言里的单词都是由小写英文字母组成有些单词很长。给定一个单词请输出出现次数最多的字母和它的出现次数。如果有多个字母出现次数相同则输出字典序最小的那个。输入格式一行包含一个仅由小写英文字母组成的单词。输出格式两行第一行输出一个英文字母第二行输出一个整数表示出现次数最多的字母以及它出现的次数。题目给了两个样例样例输入输出样例1lanqiaoa 换行 2样例2longlonglongistooo 换行 5先手动算一下第一个样例。lanqiao这个单词里l 出现了 2 次a 出现了 2 次其余字母各出现 1 次。如果只按“出现次数最多”来找l 和 a 排在同一个位置这时候就必须引入字典序规则a 在 l 前面所以答案是 a次数是 2。第二个样例里longlonglongistoo中 o 出现了 5 次是绝对多数直接输出 o 和 5 就行。1.2 考点计数、比较、字典序三件套别看这道题简单它涵盖的考点很典型我拆成了三层第一层是计数。给你一串字符你要统计每个字符出现了多少次。你可以用哈希表、字典、Counter、计数数组方式很多但核心都是“遍历一次累加频次”。第二层是比较。统计完之后要找出频次的最大值。朴实做法是遍历所有统计结果不断更新最大值装一点的做法是排序后取第一个。不管哪种本质都是在做“比较”比赛里大多数题目都要用到这个能力。第三层是字典序。这是最容易被忽略的。很多人统计完频次后直接if count[ch] max_count去更新确实能拿到最大次数但并列情况就麻烦了。为什么先出现的不一定对因为并列时还要回到字母表顺序判断谁更小。我把这道题归类为“简单但完整”的模拟题它不考复杂算法但考你代码的严谨性尤其是边界条件的处理。1.3 为什么送分题也能坑人我见过太多人在这道题上失分失分点往往不在统计逻辑而在输出和并列处理上。首先是输出格式。题目要求输出两行第一行字母第二行次数。有同学直接print(ch, cnt)打成了一行比如a 2样例肉眼看着没问题但判题系统是按标准输出逐字节比较的多一个空格或者少一个换行都算错。其次是并列处理。比如aabbcc三个字母各出现 2 次正确答案是 a。如果你在更新最大值时用的是“只要相等就更新”那最终结果很可能是 c正好和标准答案相反。还有一个隐藏点单词可能非常长。蓝桥杯的题目不会明说上限但字符串长度可能达到十万甚至百万级别。这时候如果写法低效比如每统计一个字母就把整个字典排序一次复杂度就会爆炸。虽然题目本身数据不算变态但从一开始就养成良好习惯没有坏处。2. 四种解法对比别一上来就排序这道题的解法有好几种我按从“看上去很自然”到“最适合比赛”的顺序挨个说。2.1 方案一字典统计 排序取首很多同学的直觉是先把每个字母的次数统计进字典然后对字典项排序排序规则是“次数降序字母升序”最后取第一项。word input().strip() d {} for ch in word: d[ch] d.get(ch, 0) 1 items sorted(d.items(), keylambda x: (-x[1], x[0])) print(items[0][0]) print(items[0][1])这段代码逻辑没问题样例也能过。但我们要想一个问题排序有必要吗word最多也就是 26 个不同字母排序成本确实不高时间复杂度 O(n k log k)k 26。不过这种做法把简单问题复杂化了而且在蓝桥杯这种比赛环境里排序本身不是问题问题在于排序会让你在“并列时取字典序最小”这件事上多绕一步。如果你非要排序记住排序键是(-次数, 字母)如果写成(次数, 字母)然后取最后一项也算对但更容易写错。我不推荐因为后面有更直接的方法。2.2 方案二计数数组 一次扫描推荐既然只有 26 个小写字母那就直接用长度为 26 的数组下标 0 对应 a25 对应 z。扫描一遍单词每遇到一个字母就把对应下标加一。然后从下标 0 扫到 25用严格大于的条件更新最大值下标。这种做法的好处是数组天然按字母表顺序排列下标小就代表字典序小。你只需要记住一个技巧更新最大值时用而不用这样并列时不会覆盖掉更靠前的字母。我推荐比赛里优先用这个方案。它思路简单、代码量少而且完全不需要额外引入排序、比较器这类东西降低出错概率。2.3 方案三Counter / map 的便利写法Python 选手可能会想到collections.CounterC 选手会想到std::mapJava 选手会想到HashMap。这些都能达到目的。from collections import Counter cnt Counter(input().strip()) m max(cnt.values()) ans min(ch for ch, v in cnt.items() if v m) print(ans) print(m)这个写法非常 Pythonic先算出最大次数再在所有频次等于最大值的字母里取字典序最小的。min天然就是字典序最小因为 Python 里小写字母比大小的规则就是字母表顺序。但它有一个小弱点如果输入字符串为空max()会直接报错。题目保证单词非空所以正式比赛没问题但你如果用这份代码去跑一些奇奇怪怪的题库可能会炸。另外如果字符串里混入了大写字母Counter 会把大写和小写当成不同键输出可能不符合预期。好在真题只含小写这个担忧属于预防性的。C 的std::mapchar, int按键有序遍历时天然是字典序找到最大次数后直接取第一个值等于最大次数的键即可也不用额外比较。Java 的HashMap无序要处理并列就需要遍历两遍或者维护变量相对麻烦一点。2.4 复杂度与选型总结我把四种方案的复杂度整理了一张表方便你对比方案时间复杂度空间复杂度推荐指数备注字典 排序O(n 26log26)O(26)一般思路直白但排序没必要计数数组 扫描O(n 26)O(26)很高最简单最稳Counter minO(n)O(26)高Python 下很优雅排序后取首O(n nlogn)O(n)低把简单问题写复杂了如果你准备的是蓝桥杯省赛直接用计数数组方案保证不翻车。如果你平时刷题偏爱 PythonCounter min 也是好写法但要留意空串边界。3. 代码实现与逐行解读3.1 Python 版本先上最推荐的版本s input().strip() cnt [0] * 26 for ch in s: cnt[ord(ch) - ord(a)] 1 ans 0 for i in range(1, 26): if cnt[i] cnt[ans]: ans i print(chr(ans ord(a))) print(cnt[ans])逐行解释。input().strip()读取一行并去掉首尾空白字符。很多人忽略.strip()其实直接input()也能过因为单词没有空格但养成习惯总归好。cnt [0] * 26创建长度为 26 的数组下标 0 表示 a25 表示 z。Python 里[0] * 26不会产生引用共享问题可以放心用。ord(ch) - ord(a)把字符转成 0 到 25 的下标。比如ord(a) - ord(a) 0ord(b) - ord(a) 1。for i in range(1, 26)直接从 1 开始因为下标 0 已经作为初始候选了。如果从 0 开始cnt[i] cnt[ans]第一次比较时i ans 0不会更新白白浪费一次循环。关键点在if cnt[i] cnt[ans]。这里用的是严格大于。当cnt[i] cnt[ans]时不更新 ans保证下标小的字母胜出。数组下标天然就是字典序顺序所以这行代码同时解决了“找最大次数”和“并列取最小字典序”两个问题。输出时用chr(ans ord(a))把下标还原成字符然后第二行输出次数。注意输出是两行不是一行。3.2 C 版本C 写法和 Python 本质上一样但有一个新手特别容易踩的坑局部数组不自动清零。#include bits/stdc.h using namespace std; int cnt[26]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); string s; cin s; for (char c : s) { cnt[c - a]; } int best 0; for (int i 1; i 26; i) { if (cnt[i] cnt[best]) { best i; } } cout char(a best) \n; cout cnt[best] \n; return 0; }我在 main 外面定义int cnt[26]因为全局变量会被零初始化。如果你把数组写进 main 里面比如int main() { int cnt[26]; ... }那cnt里的值是随机的很可能不是 0统计结果直接错乱。解决办法是把数组初始化成int cnt[26] {};或者int cnt[26] {0};。这个坑我在集训队里见过无数次尤其是从 Java 转 C 的同学更容易中招。cin s会自动跳过空白字符并读取连续的单词题目说单词不含空格所以这样读没问题。如果你非得用getline(cin, s)要注意可能把上一行残留的换行符读进来导致字符串开头是空行。稳妥做法就是普通cin s。char(a best)把下标还原成字母。C 里 char 和 int 可以混算a 0是aa 1是b非常自然。bits/stdc.h是竞赛常用万能头文件蓝桥杯官方评测环境支持 g可以正常编译。如果你在普通 IDE 里用可能需要额外配置如果担心兼容性改成iostream和string也完全够用。3.3 Java 版本Java 选手注意类名必须是Main否则编译直接不通过这也是蓝桥杯的老规矩。import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); String s sc.next(); int[] cnt new int[26]; for (int i 0; i s.length(); i) { cnt[s.charAt(i) - a]; } int best 0; for (int i 1; i 26; i) { if (cnt[i] cnt[best]) { best i; } } System.out.println((char) (a best)); System.out.println(cnt[best]); sc.close(); } }sc.next()读取到空白分隔符为止正好匹配单词输入。如果你用nextLine()就要考虑换行符问题。new int[26]会自动初始化为 0Java 在这点上比 C 安全。3.4 输出格式最容易丢分的地方我再说一遍输出是两行不是一行。蓝桥杯是黑盒判题比较的是你的程序输出和标准答案的差异任何多余的字符、缺失的换行都会导致 WA。我建议在写完代码后自己手动跑一遍样例肉眼确认一下输出。特别是用print时如果你写成print(ans, cnt[ans])那输出会变成a 2一行。你看着顺眼判题系统看着陌生直接零分。一个小技巧提交前把样例输入复制进程序对照题目要求的输出格式逐字符检查。换行、空格、大小写都要一致。4. 常见错误与排查技巧实录4.1 并列时输出了字母表中靠后的字母这是我见到最多的问题。典型错误代码如下for ch in s: cnt[ord(ch) - 97] 1 max_val 0 ans for i in range(26): if cnt[i] max_val: max_val cnt[i] ans chr(i 97)注意这里是一旦后面的字母次数追平就会覆盖掉前面的字母。对于lanqiao遍历到 l 时次数 2ans 变成 l继续遍历到 a 时a 的次数也是 2因为ans 会被更新成 a但你继续遍历后面的 n、q、i、o 时它们的次数都是 1不会触发更新。这个例子还能碰巧得到 a。如果字母顺序反过来比如单词是alqiao你先统计到 a 再统计到 l同样因为ans 最终是 l就错了。解决方案就是严格大于。原理是数组下标本身就代表字典序遇到相等时不更新保留更小的下标最终结果就是字典序最小的那个。4.2 把答案打到了一行很多人觉得题目“输出出现次数最多的字母和它的出现次数”就是一行输出。但原题格式明确写的是两行。竞赛里最冤的丢分方式就是这种格式错乱。我记得有一次训练赛一个学弟样例输出a 2本地自测完全一样但他把样例的换行忽略掉了。后来我把题目原文截图给他看他才发现标准答案里 a 和 2 是两行。从那以后我养成一个习惯凡是有多个输出项先看题目是“空格分隔”还是“分行输出”绝不靠猜。4.3 读取输入时把换行符也算进去了如果你用getline(cin, s)配合之前的cin操作很可能读出来一个空字符串或者开头带换行符。举个例子int n; cin n; string s; getline(cin, s);这时候getline读到的不是单词而是cin n后残留的换行符。解决方法是先cin.ignore()清掉缓冲区或者干脆全部用cin s。Python 里input()本来就以换行符为结尾直接把换行符吃掉了没有这个问题。但如果你用sys.stdin.read()做自定义读取就要考虑字符串末尾潜在的空格和换行。4.4 HashMap 顺序带来的迷惑Java 的HashMap不保证顺序C 的unordered_map也不保证顺序。如果你用它们统计次数并列时想“从 map 里找出字典序最小的字母”就必须自己写循环比较而不能默认第一次遍历到的键就是答案。如果你用 C 的std::mapchar, int它按键排序遍历顺序就是字典序。Java 的TreeMap也类似。但性能上unordered_map和HashMap更快只是处理并列时要多写几行代码。最省心的还是变长数组既快又不用考虑顺序。4.5 本地能过提交却零分对照判题环境蓝桥杯和很多在线评测系统一样判题环境和你本地可能有差异。常见情况包括Java 类名不是MainC 用了本地才有的头文件Python 版本不一致导致Counter等接口行为差异或者代码里带了package声明。我的经验是提交前做一次“干净环境检查”。把代码复制到新建文件里确认没有多余的打印语句确认没有自定义包名确认入口函数命名正确。然后想想判题机用的可能是旧版编译器尽量不用太新的语法特性。还有一些老生常谈的问题别用gets()它在新的 C 标准里已经删掉了别用system(pause)判题时会卡住别在输出末尾加奇怪的空格。4.6 用随机数据对拍验证当你想确认自己的代码是不是真的符合题意可以写一个对拍脚本用暴力解法和优化解法同时跑大量随机数据对比输出。这是我调试竞赛题最常用的手段。import random def slow(s): from collections import Counter c Counter(s) m max(c.values()) return min(ch for ch, v in c.items() if v m), m def fast(s): cnt [0] * 26 for ch in s: cnt[ord(ch) - 97] 1 p 0 for i in range(1, 26): if cnt[i] cnt[p]: p i return chr(97 p), cnt[p] for _ in range(10000): n random.randint(1, 30) s .join(random.choice(abcdefghijklmnopqrstuvwxyz) for _ in range(n)) a slow(s) b fast(s) if a ! b: print(Mismatch, s, a, b) break else: print(All tests passed.)这里的slow函数用 Counter 加 minfast函数用计数数组两个思路互相印证。如果一万组随机数据输出全部一致基本可以放心。对于单词分析这种小题对拍似乎有点小题大做但它培养的是一种严谨习惯。以后遇到复杂题这个套路能帮你快速找到隐藏 bug。5. 从单词分析看蓝桥杯第一题的复习策略5.1 送分题往往决定比赛心态蓝桥杯省赛的题量不算少难度分布却有明显梯队。第一题或者前几题通常都是这种“读题十分钟代码五分钟”的送分题。它们的共同特点是不考高级数据结构和复杂算法考的是基础代码能力、字符串处理、简单模拟、数学计算。很多同学觉得这种题太简单不值得复习于是上来就刷动态规划、图论、线段树。结果比赛时第一题因为输出格式错了卡了二十分钟心态直接崩掉后面的题也发挥失常。我个人的建议是正式比赛前至少把近三年的省赛真题前两题全部手写一遍保证速度和质量都过关。5.2 一类题统计次数类题目单词分析本质上属于“统计次数”大家族。这个家族还有不少亲戚比如统计一个数字在某个范围内出现的次数统计字符串中不同字符的数量统计各分数段人数统计二进制中 1 的个数。它们的共性都是“遍历 计数 求极值”换个马甲你也要能认出来。蓝桥杯热搜里经常出现“蓝桥杯单片机国赛客观题”“蓝桥杯嵌入式”“蓝桥杯EDA”这些关键词很多人以为算法题和硬件题是两条平行线。我的体会是不管是嵌入式还是纯软件赛道第一题基本都是这种“稳拿分”的题目。把单词分析这类基础题吃透再去啃那些偏硬件或偏工程的模块心里会更有底。5.3 蓝桥杯题库里值得顺带刷的“邻居题”我建议刷完单词分析后顺手做下面几道同类题它们出现在省赛真题或模拟题里的概率很高成绩统计给出若干百分制成绩输出及格率和优秀率。核心是计数和格式化输出注意百分号保留小数位。数字统计给定 L 到 R 区间统计某个数字出现的次数。可以用字符串拼接再计数也可以逐位拆分。回文判断判断一个字符串是否为回文或者统计字符串里有多少个回文子串。前者是入门后者是进阶。字母图形按规律输出字符矩阵。这道题的核心是找下标规律。质数判断与素数筛统计区间内质数个数。这类题和统计次数结合紧密。每道题做的时候都问自己三个问题边界条件是什么极端数据是什么如果不看题解我能不能独立写对把这三个问题想清楚比盲目刷五十道题有用。5.4 备赛节奏建议如果你现在离比赛还有几个月我的建议分三个阶段。第一阶段把真题里所有第一题、第二题过一遍目标是“又快又稳”见到就能写写了就能过。第二阶段开始按专题刷中等题比如枚举、贪心、二分、动态规划的入门题。第三阶段每周做一次全真模拟完全按照比赛时间和规则来训练自己在时间压力下的输出格式、变量命名、代码调试节奏。单词分析这种题就属于第一阶段的必刷清单。别看它简单它承载的任务是让你在比赛最开始快速进入状态、建立信心。6. 一点个人经验补充写到最后我忍不住想分享一个小习惯。每次写完这类输出型题目我都不会急着提交而是先在草稿纸上画一个检查清单输入读取方式对不对数组初始化有没有问题比较条件是还是输出是几行、中间有没有空格极端数据会不会越界。整个过程大概三十秒但能避免大部分无谓的失分。另外建议你在本地把样例跑一遍后故意构造一个并列的输入比如aabbcc再构造一个只有单个字母的输入比如zzzzz。前者验证字典序并列逻辑后者验证最大次数统计逻辑。如果这两种边界情况都能输出正确答案这道题基本就稳了。单词分析题本身不复杂但它很像是蓝桥杯比赛的一道缩影题目读得懂代码写得快细节却藏着不少门道。把这种送分题的细节抠到位那些真正拉开差距的难题你才有足够的余裕去慢慢处理。希望这篇复盘能帮你少踩几个坑比赛时稳稳拿到该拿的分。
返回列表