蓝桥杯算法入门:Anagrams判断与正反三角形输出的核心规律 1. 先搞清楚这两个题到底在考什么刷蓝桥训练题时把“Anagrams问题”和“输出正反三角形”放到一起乍一看有点莫名其妙一个是字符串判断题一个是图案输出题八竿子打不着。但真正动手做一遍就会发现这两个题其实是同一个核心能力的两个侧面——把现实问题拆成可计算的规律再用代码精确地表达出来。这种能力说白了就是算法入门最重要的基本功。先说Anagrams问题。Anagram这个词来自英文意思是“由相同字母重新排列形成的词”比如“listen”和“silent”字母集合完全一样只是顺序不同。给定两个字符串让你判断它们是不是这种关系这就是题目核心。蓝桥这道题还有一个更贴近实际的小设定比较时忽略大小写也忽略空格。也就是说“Hello World”和“world hello”在题目逻辑里会被当作一组Anagrams。这个细节很关键因为它直接决定了你的解题方向——不是简单判断字符串是否相等而是要比较“字母的多重集合”是否一致。“多重集合”这个概念听起来有点学术其实很好懂。你想象两副扑克牌把牌全倒出来摊在桌上不去管原来的排列顺序只问一副牌里的每种花色点数是否和另一副完全一样。这就是Anagrams问题的本质不看顺序只看“每个字母出现了多少次”。如果两串字符里每个字母的出现次数都相同那它们就是Anagrams。输出正反三角形这道题就直白多了。输入一个正整数n输出由星号组成的三角形图案正着来一遍再倒着来一遍。看起来只是打印几个星号但很多人第一次写的时候反而栽跟头——不是多了一个空格就是少了一行或者行首缩进对不上。原因很简单这题本质上考验的是“把每个位置上的字符数与行号挂钩”的能力。你需要在动手写代码之前先在草稿纸上把第1行有几个空格、几个星号第2行又有几个空格、几个星号全部总结成数学公式。公式对了代码只是翻译问题。两个题放到同一份训练计划里其实挺合理。Anagrams考的是“看不见的规律”你要从题目描述里抽取出字母计数这个逻辑核心三角形考的是“看得见的规律”形状就摆在那里但你要把形状数字化、公式化还得注意输出格式这种竞赛中最容易扣分的细节。无论哪个都是把模糊需求变成严格逻辑的完整练习。我自己带过不少刚开始刷题的朋友一个明显的共性是很多人不是不会写代码而是不会“读题”。看到Anagrams直接上来比较字符串是否相等忽略大小写和空格的要求完全没进脑子看到三角形不知道先列出每行的空格数和星号数公式一上来就手写for循环写着写着下标就乱了。这篇文章就把这两个题从读题到实现完整拆开把我试过的方法、踩过的坑、蓝桥系统判分时容易忽略的细节全部梳理一遍。2. Anagrams问题的完整拆解2.1 题面细节与隐藏要求蓝桥训练里的Anagrams问题原题通常会这样描述输入两个字符串判断它们是否为Anagrams如果是输出Y否则输出N。重点在于题目明确写了“只考虑字母忽略大小写和空格”。这句话至少包含三层意思。第一层空格要被剔除。意味着你在读取字符串后得先把所有空格符号剔除掉剩下的非空格字符才参与比较。第二层大小写不区分。A和a算同一个字母这要求处理时对字母做统一转换要么全部转大写要么全部转小写。第三层只考虑字母。这句话有两种理解一种是空格被忽略其余字符都参与比较另一种是除了字母以外所有字符都不参与比较。蓝桥原题多数采用的是“空格忽略但不强制剔除其他符号”的口径稳妥的做法是在预处理时直接只保留字母其他字符一律跳过。这样无论如何解析都是安全的。另一个容易忽略的点是输入形式。题面说的是“输入两个字符串”但这两个字符串可能都包含空格所以不能简单地用scanf(%s)去读。用C语言做这道题得用gets或者fgets整行读取用Java就用BufferedReader读整行。很多人在本地测试时没问题是因为测试数据恰好没有空格一提交到蓝桥评测系统就出现答案错误十有八九是读取方式的问题。题目还常常有多组输入的要求不是读一组判断一组就结束而是要循环处理直到文件结束。蓝桥的训练数据有时会刻意塞多组输入来测试代码的健壮性只处理一组数据的代码提交上去只能拿到部分分数。我建议一开始就把代码写成循环读取每组输入独立处理输出独立换行这样无论评测数据里有几组代码都能正确应对。2.2 三种解法的原理对比处理Anagrams判断主流方案有三种排序后比较、哈希计数、三计数器差减法。三种思路各有特点适用场景也不同我分别说一下。第一种排序后比较。把两个字符串分别排序如果排序后完全相同说明它们字母构成一致。C语言里可以用qsortC可以直接sortJava可以先把字符串转成字符数组再排序。这种做法代码量小逻辑清晰缺点是时间复杂度是O(n log n)字符串很长时性能不如计数法。更重要的是对于本题有“忽略空格和大小写”的要求你还得先做预处理转大小写、去空格。如果忘了过滤空格两个明明互为Anagrams的句子排序后不相等直接判断错误。第二种计数数组法。开两个长度为26的数组遍历两个字符串遇到字母就统一转成小写或大写然后在对应位置计数。遍历结束后逐个比较两个数组的每一位完全相同就是Anagrams。这种做法的时间复杂度是O(n)代码同样很简短而且天然处理了字母大小写问题只需要额外跳过空格。题目里明确说明忽略空格所以遇到空格时continue掉就行。第三种三计数器差减法。这种做法的思路很巧妙只开一个长度为26的计数数组遍历第一个字符串时对应位置加1遍历第二个字符串时对应位置减1最后看数组里是不是每一位都归零。一次遍历串1加计数再遍历串2减计数省去了一个数组的开销代码也简洁不少。但需要注意边界情况如果两个字符串长度悬殊理论上早就该提前退出以节省时间不过蓝桥这道题的数据规模通常不大这个优化提不提都行。从蓝桥训练题的实际数据规模来看n是百万级别的可能性不大所以三种方案在时间上都不会有问题。真正选哪种取决于你写代码时的舒适度。我个人的建议是入门阶段优先掌握计数数组法因为它的思路最直观从“多重集合相等”这个本质出发一个数组就能讲清楚原理。排序法是考场上快速写出可用代码的备选方案适合时间紧、没太多时间思考的情况。三计数器差减法在初学阶段容易想不明白“为什么遍历第二个串时要减”一旦理解后反而最省事。2.3 为什么我最终选择了计数数组方案Anagrams的判断核心是字母多集相等计数数组法在这个语义下是最匹配的。你用两个数组分别记录两个字符串的字母构成然后逐位比较每一步的逻辑都和题目要表达的意思一一对应出错了也容易排查。排序法虽然代码短但它把问题从“字母集合相等”变成了“排序后字符串相等”语义上隔了一层。一旦题目要求过滤空格或者只考虑字母排序法的预处理反而更容易出错。我举个例子你就明白了。假设输入是“listen”和“silent”排序法能得到两个完全一致的字符串没问题。但输入是“hello world”和“world hello”时两个串都含空格排序前排不排空格直接影响结果。如果你只排了字母序列那么排序后仍然一致如果连空格一起排进去排序后不一致题目本来该输出Y的你的代码输出N直接判错。计数数组法因为处理流程中“遇空格就跳过”是显式写在代码里的天然避免了这种坑。还有一点是输出格式的坑。蓝桥的Anagrams问题要求输出单个字符Y或N后面跟换行。有些同学在循环处理多组数据时会在每组输出后多打印一个空行这在本地看不出问题但评测系统是按字节比对输出结果的多一个空格多一个空行都会导致Presentation Error。后面我会专门用一个章节讲这类格式问题这里先提个醒输出Y或N时字符串里不要带任何多余字符换行用printf(\n)就好。3. 输出正反三角形的规律与实现3.1 用行号把所有数量关系表达出来图案输出的题核心永远是找规律。所谓“规律”就是每一行内容与行号之间的数学关系。输出正反三角形先只看正三角形那部分。以n5为例图案长这样* *** ***** ******* *********第一行1个星号第二行3个第三行5个第i行的星号数是2i-1。空格数是n-i用来实现居中对齐。这个规律其实很好验证第一行n-1等于4个空格第二行n-2等于3个空格依次递减。一旦把“第i行 (n-i)个空格 (2i-1)个星号”这个公式写出来代码就是两分钟的事情。反三角形就是正三角形的镜像。继续以n5为例图案长这样********* ******* ***** *** *第i行这里从上往下数的空格数是i-1星号数是2(n-i)1。也可以换一种写法让行号从n递减到1空格数用n-i计算星号数还是2i-1。两种写法本质相同只是循环变量的方向不同。有人看到这里会问这种题有什么好讲的不就是套公式吗但实际写的时候很多人会遇到一个很奇怪的问题——明明公式是对的输出却不对。我在旁边帮人调试时就见过好几次原因是循环里多写了一个空格。比如有人写成“先打印一个空格再打印n-i个空格”导致第一行有两个前导空格整个图案往右偏移了一格。这种错在本地屏幕上看起来不严重但蓝桥的判分是按字节比对多一个空格就是错。我建议初学者先用一张表把规律固化下来。以n5的正三角为例可以列出下表行号i空格数(n-i)星号数(2i-1)141233325417509对照这张表写循环就是三个循环外层循环管行号内层第一个循环打印空格第二个循环打印星号最后打印换行。逻辑上完全不会有歧义。写完正三角反三角的表格同理然后把两个图案拼在一起就可以输出一个完整沙漏。3.2 沙漏、菱形与题目变种前文说的正反三角形有时题目会要求连续输出正三角在上、反三角在下中间不空行组合起来像个沙漏。实现方式就是在同一个外层循环里按两段处理。更常见的变体是输出菱形本质就是把正三角形和反三角形拼接中间一行共用不重复输出。理解了三角形公式后菱形无非是把反三角的起始行号改一下或者单独处理中间行。还有一类变种是数字三角形。比如输出对称数字三角行的内容从1递增到2i-1再递减回1。公式思路完全一样区别只在于每行的内容从星号变成了数字。这种题在蓝桥里出现频率不低核心就是“内容由行号决定”这个思想。我在实际练习中总结了一条经验图案题的变体再多最终都可以归约到“每行内容 f(行号)”。只要你能画出样例图案在旁边标出第1行、第2行、第3行分别有几个空格几个字符规律就自动浮现了。不要凭感觉写不要边写边试先把表格列出来。3.3 输出格式的严格要求图案题的得分除了规律找得对不对很大程度上取决于格式是否精确。蓝桥评测系统把程序输出的全部字节与标准答案比对多一个空格、多一个空行、行首少几个空格、末尾多一个换行都可能被判为错误。这类错误通常以Presentation Error的形式报出来意思是“输出结果不对或者格式有问题”。应对方式只有一种严格按照样例推导输出格式。题目样例给出的图案是什么样的你的程序就必须输出成什么样。一般来说有这么几条硬性规则行首空格必须对齐每行星号打印完后直接换行行尾不要再补空格两个三角形之间是否需要空行以样例为准。很多人在本地运行时终端会把行尾空格显示得很不明显到了评测系统就出问题。稳妥的做法是写完代码后用一个小技巧自查把输出重定向到文件里用十六进制查看文件内容多出来的空格立刻现形。这个技巧我在后面“本地自测”部分会细说。4. 完整可运行的参考代码4.1 Anagrams问题C语言实现下面给出一份完整的C语言实现它支持多组输入读取带空格的整行字符串只统计字母并忽略大小写最后逐组输出Y或N。#include stdio.h #include string.h #include ctype.h #define MAXLEN 1005 void countLetters(const char *s, int cnt[]) { for (int i 0; s[i] ! \0; i) { if (isalpha(s[i])) { cnt[tolower(s[i]) - a]; } } } int main() { char s1[MAXLEN], s2[MAXLEN]; while (fgets(s1, MAXLEN, stdin)) { if (!fgets(s2, MAXLEN, stdin)) { break; } // 去掉fgets可能读进来的换行符 s1[strcspn(s1, \n)] \0; s2[strcspn(s2, \n)] \0; int cnt1[26] {0}; int cnt2[26] {0}; countLetters(s1, cnt1); countLetters(s2, cnt2); int flag 1; for (int i 0; i 26; i) { if (cnt1[i] ! cnt2[i]) { flag 0; break; } } printf(%c\n, flag ? Y : N); } return 0; }代码思路很直白。fgets逐行读取因为字符串里可能含空格scanf的%s没法用。fgets会把换行符也读进来所以用strcspn找到换行符位置并替换成字符串结束符。countLetters函数遍历字符串遇到字母就统一转换成小写在对应下标里计数。最后比一比两个计数数组全等就是Y。这里有一个细节值得提醒countLetters用isalpha判断字符是否为字母然后用tolower转成小写这样A和a落在同一个计数位置。如果你不放心编译环境的字符集这种写法是基于标准C库的在蓝桥的GCC环境里没问题。还有一种省事的写法是直接把两个串里的字母统一转成大写再计数效果一样。4.2 正反三角形图案输出C实现黄金搭档第二题完整实现如下。假设输入n输出由星号组成的正三角形在上、反三角形在下的组合。#include stdio.h int main() { int n; while (scanf(%d, n) ! EOF) { // 正三角形第i行有n-i个空格2i-1个星号 for (int i 1; i n; i) { for (int j 1; j n - i; j) { printf( ); } for (int j 1; j 2 * i - 1; j) { printf(*); } printf(\n); } // 反三角形第i行从1到n有i-1个空格2(n-i)1个星号 for (int i 1; i n; i) { for (int j 1; j i - 1; j) { printf( ); } for (int j 1; j 2 * (n - i) 1; j) { printf(*); } printf(\n); } } return 0; }注意外层循环的行号从1开始这是我刻意保持的写法。很多教材喜欢从0开始但0-based写法的公式容易带出n-1这种偏移量初学者经常绕晕。1-based的写法虽然内层循环的终止条件会多出一些-1或1但行号和自然语言里的“第几行”完全对应更好排查问题。输出正反三角形之后如果需要输出菱形只需把反三角形的第一行去掉因为正三角形的最后一行和反三角形的第一行中间行重合。我在实际刷题过程中发现同一个公式稍微调整循环边界就能适配多个变体所以掌握1-based写法后多练几个变体收益很高。4.3 Java环境下的快速实现对比蓝桥的Java组同样会考这类题思路和C一致但代码风格上有几个不同点。Anagrams问题的Java实现可以这样写import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { BufferedReader br new BufferedReader(new InputStreamReader(System.in)); String s1; while ((s1 br.readLine()) ! null) { String s2 br.readLine(); if (s2 null) { break; } int[] cnt new int[26]; countLetters(s1, cnt, 1); countLetters(s2, cnt, -1); boolean ok true; for (int c : cnt) { if (c ! 0) { ok false; break; } } System.out.println(ok ? Y : N); } } private static void countLetters(String s, int[] cnt, int delta) { for (char ch : s.toCharArray()) { if (Character.isLetter(ch)) { cnt[Character.toLowerCase(ch) - a] delta; } } } }这里直接用了三计数器差减法一个cnt数组就完成了两个串的统计。读串用readLine天然处理了带空格的问题。Character.isLetter和Character.toLowerCase对应C里的isalpha和tolower。三角形的Java实现同样简单用System.out.print逐字符输出即可。不过Java的输出效率比C的printf慢如果n很大、图案行数很多建议用StringBuilder把整个图案拼接完再一次性输出。蓝桥训练题的n通常不大直接print问题也不大但养成用StringBuilder的习惯对后续解更大规模的题有好处。4.4 本地自测的小技巧本地写代码时肉眼观察输出是否正确往往是不可靠的。尤其是图案题行尾的空格在多数编辑器和终端里看不出来但评测系统能看出。我推荐一个自测方法把程序输出重定向到文件然后用带十六进制视图的编辑器查看或者用命令行工具检查。Linux和Mac下可以用cat -A查看文件的每一行行尾的换行符显示为$空格显示为普通空格一眼就能看出有没有多余空格。Windows下可以用记事本打开文件打开“显示所有字符”功能。如果你用的是VS Code装一个“Hexdump”插件直接看字节内容是终极方法。对于Anagrams这种判断类题目更实用的自测方式是构造边界用例。比如“A”和“a”应该输出Y“hello world”和“world hello”应该输出Y“abc”和“abd”应该输出N两组数据连续输入时输出也应该连续两行“Y/N”中间不能有多余空行。把这些用例提前准备好每改一次代码就全跑一遍能省去大量反复提交评测的时间。5. 实战中容易踩的坑5.1 输入读取的坑Anagrams问题第一个坑就是我们前面反复提到的“含空格字符串的读取”。有些同学习惯用scanf(%s)本地测试时输入恰好没有空格样例过了就提交结果在评测系统上WAWrong Answer怎么查都查不出问题。原因是评测数据里确实有带空格的字符串scanf按空白符分割输入读到的只是第一个单词后面的内容全被当作下一组数据的开头整个判断逻辑全乱。C语言里读取带空格的单行字符串传统做法是gets但这个函数在新版C标准里已经标记为过时存在缓冲区溢出风险蓝桥官方环境虽然一般还是能编译通过但不推荐。fgets是更规范的选择注意它会保留换行符需要手动清掉。还有一种做法是用scanf(%[^\n])这样的格式说明符读取到换行前加上换行匹配符处理行尾但可读性不如fgets而且容易留下尾换行符没消费的问题。我调试多组输入时折腾过很久最后稳定下来的方案就是fgets加strcspn清理换行符。Java里用BufferedReader.readLine()读取整行天然安全。唯一要注意的是多组输入时第一行可能为空字符串需要处理一下空串的情况。不过竞赛数据一般不会故意给空串遇到这种情况直接当成普通字符串处理即可。5.2 演示错误与格式化输出蓝桥的旧版评测系统里有种特殊的返回结果叫Presentation Error简称PE含义是“输出格式有问题”。它通常出现在两种情况输出内容本身正确但多加了空格或空行或者行尾多打了空格。PE和WA在很多评测系统里会分开标识蓝桥早期版本会区分。现代系统大多统一归为WA但为了稳妥还是一开始就别格式出错。我见过最典型的PE案例是图案题程序在每行星号结束后多打了一个空格。在本地终端看行尾空格几乎无感知提交后却不能得分。排查方法就是把输出重定向到文件对照样例逐字节检查。还有一类问题出在换行符上。Windows环境下的换行是\r\n蓝桥评测环境是Linux只认\n。如果在Windows下用记事本编辑代码代码文件本身的换行符可能被保存成\r\n提交到Linux上编译运行时printf输出的换行通常是\n问题不大。但fgets在Windows下读取文件时会把\r\n都读进来这时strcspn只清掉\n\r残留在字符串末尾会被当成普通字符参与统计导致判断错误。解决办法是清理换行符时同时清掉\r先看字符串末尾是不是\r是就置为\0再看是不是\n一并清掉。这个细节在Windows本地调试、提交到Linux评测时非常关键我在实战中踩过一次排查了很久。5.3 边界与特殊输入Anagrams问题的边界情况主要围绕空串和单字符。如果两个字符串都是空串或者只含空格理论上它们是Anagrams吗按题目“只考虑字母忽略空格”的规则两者都退化成空集合应该输出Y。计数数组全为0比较时全相等自然输出Y代码不用特判。如果只含字母但数量不同例如“a”和“aa”计数数组一位不相等输出N。这些逻辑在计数数组法下都是自动正确的。三角形题的边界就是n等于1和n等于2。n等于1时正三角一行一个星号反三角也是一行一个星号组合起来两行。n等于2时图案很小但如果公式写错很容易发现。还有n很大的情况只要循环边界正确输出长度线性增长不会有溢出问题。唯一要注意的是外层循环和外层循环不要套错。5.4 排查问题时的思路顺序当代码提交后报错我不建议盲目改代码再试。先按这个顺序排查第一步检查输入输出方式是否符合题面要求尤其是字符串含空格时读取方式对不对第二步检查题目要求是否全部落实比如是否忽略大小写、是否忽略空格、是否多组输入循环第三步用边界用例本地验证看看输出逻辑是否有系统性偏差第四步检查格式细节重点看行尾空格和空行。把每一步都排除后再去读代码逻辑往往很快就能定位问题。很多时候错误出在读题不仔细而代码本身逻辑没问题。6. 从这两个题延伸出去的经验Anagrams问题背后的“多重集合相等”思想在竞赛中会反复出现。比如字符串重组匹配、判断句子是否由相同单词构成、哈希统计后比较等。计数数组这个工具也不只用来处理字母处理数字范围固定的频次统计问题时完全通用。你一旦掌握“用一个数组记录频次差异”的思路后面遇到很多统计类问题都会豁然开朗。输出正反三角形这类图案题看起来偏门但它是二维数组、模拟、规律题的入门标尺。蓝桥后续的“回形取数”“蛇形填数”等二维数组模拟题思路本质都是“用坐标变化描述图案规律”和三角形题一脉相承。把三角形输出练到闭着眼睛能写的熟练度处理更复杂的坐标变换时心里就有底。给刚起步的同学一个刷题路径建议拿到一道题先在纸上手动模拟样例把每一步的变化写下来然后尝试用数学公式描述这种变化最后才动手写代码。这样的流程刚开始会偏慢做十道题之后就明显感觉到读题和建模的速度上来了。我在教朋友刷题时发现凡是坚持这个流程的一两个星期后独立解题能力都会有明显提升。最后说一个我个人的体会刷题时不要怕用最笨的方法解出题目重要的是把解题过程完整走一遍再回头看能不能优化。Anagrams问题我第一次用的是排序法单组数据能过多组输入时才发现循环结构没处理好后来改成计数数组法才顺滑。三角形的题我第一次写直接把循环边界搞混了最后画了个表格才理清思路。这些初次的“笨”反而是理解题目本质最好的老师。