☰
叠筐问题全解析:从逐层填充到坐标公式,避开所有OJ格式坑
2026/10/17 3:06:31 网站建设 项目流程

叠筐问题,是刷过老牌OJ平台的人绕不开的一道经典模拟题,题号经常被简写成 2074。第一次碰到它的时候,我心想:这不就是画个回字框吗?把两个字符轮着填进去就行了。结果真上手一提交,WA 得特别干脆。第一次挂在多组输出之间的空行上,第二次挂在行尾空格上。等我把图案一点点拆开,才发现这题真正的价值不在于“会不会画框”,而在于能不能把视觉上的规律转化成一段可以用循环表达的逻辑,同时在输出格式这种细节上做到滴水不漏。这篇文章就从题目拆解、规律分析、两版代码到踩坑记录,把叠筐问题完整讲透。

1. 先把题目搓清楚:叠筐到底在考什么

1.1 输入输出细节

输入格式很简单:每个测试用例是一行,包含一个奇数 n(3 到 80 之间,实际因为要是奇数,最大只会到 79),以及两个可见字符,字符之间用空格分隔。输入一直读到文件结尾为止。输出对应一张 n 行 n 列的“叠筐”图案,规则如下:

最外圈使用第一个字符,向里一圈使用第二个字符,再向里一圈换回第一个字符,一圈一圈交替直到中心。同时,最外圈的四个角必须挖成空格。输出时每行的行尾不能出现多余空格,多个测试用例之间用一行空行隔开,第一个用例之前和最后一个用例之后都不能有多余空行。

读题时最容易被忽略的有两个地方。第一,中心字符是不固定的,它取决于 n 的奇偶相关计算;第二,四角挖空之后,行首和行尾都可能出现空格,但行尾空格必须去掉。这两个点,恰恰就是叠筐问题成为“新手劝退题”的根源。

1.2 一个容易先入为主的结论

看样例的时候,很多人会得出“中心是第二个字符”的结论。拿输入11 B A来说,输出的中心确实是 A,也就是后输入的第二个字符。但这个结论是经不起推敲的。换个n=5再试,最外层固定是 B,往里一圈是 A,再往里的中心又变回 B 了。

所以正确规律根本不是“中心固定为第二个字符”,而是从外到内按层号交替:第 0 层(最外层)用第一个字符,第 1 层用第二个字符,第 2 层用第一个字符……中心用哪个字符,取决于 n/2 那一层的序号是奇数还是偶数。

这个误解之所以流行,是因为出题人选的样例 n=11 恰好让中心落在了第二个字符上。如果样例换成 n=5,大家的结论恐怕就会反过来。这也提醒我刷题时不能只看一两个样例就总结规则,边界数据才是检验规律的试金石。

1.3 为什么说它是模拟题而不是数学题

叠筐问题没有用到任何算法,不需要排序、搜索、动态规划。它考察的是一种“按规则构造结果”的能力,也就是模拟题的核心。模拟题的难点从来不是规则本身有多复杂,而是规则里的边界条件够不够清晰。叠筐题目把“层号奇偶交替”“四角挖空”“行尾格式控制”三个规则揉在一起,正好构成了一道非常典型、非常有训练价值的模拟题。

2. 找规律:把图案翻译成坐标和层号

2.1 先画几个最小用例

从 n=3 开始,输入3 B A,输出是这样的:

B BAB B

最外圈是 B,中心是 A,四个角被挖空后,第一行只剩“空格 + B”,第三行同理。这里就能看出行尾处理的重要性:假如第一行输出成“空格 + B + 空格”,末尾那个空格就是多余的,直接 WA。

再画 n=5,输入5 B A,输出:

BBB BAAAB BABAB BAAAB BBB

同样是最外圈 B,第二圈 A,中心回到 B。看到这个例子,前面“中心永远是 A”的说法就不攻自破了。

再看 n=7,输入7 B A,输出:

BBBBB BAAAAAB BABBBAB BABABAB BABBBAB BAAAAAB BBBBB

三组小样例放在一起,规律已经很清晰了:从外到内一圈一圈交替,四角挖空。

2.2 用层号统一描述

对所有奇数 n,定义一个变量 layer,表示某个位置到四个边界的最小距离:

layer = min(i, j, n-1-i, n-1-j)

这个值从外到内递增,恰好对应叠筐的一圈一圈。最外层 layer=0,第二层 layer=1,最内层即中心是 layer=n/2。每一层用什么字符只由 layer 的奇偶性决定:

layer % 2 == 0 时,使用第一个字符 layer % 2 == 1 时,使用第二个字符

四个角在哪里?就是layer == 0,同时 i 和 j 分别处于边界的那四个点。具体来说,是(0,0)、(0,n-1)、(n-1,0)、(n-1,n-1)。这四个点不能参与正常字符填充,必须单独置为空格。

2.3 为什么这个规律对所有奇数 n 都自洽

因为最外层的 layer 永远是 0,是偶数层,所以最外圈永远是第一个字符。而中心那一层的 layer 是 n/2,它可能是偶数也可能是奇数,因此中心字符不固定。这个描述完美兼容 n=3、n=5、n=7、n=11 所有情况,代码里只需要判断layer % 2,不需要特判“中心用哪个字符”,逻辑非常干净。

3. 第一版实现:二维数组逐层填充

3.1 完整代码

最推荐的写法是先开一个二维字符数组,把整张图填好再统一输出。这样虽然多占一点内存,但思路最直,后续处理格式也最方便。

#include <stdio.h> #include <string.h> char pic[85][85]; int main() { int n, first = 1; char ch1, ch2; while (scanf("%d %c %c", &n, &ch1, &ch2) == 3) { if (!first) { printf("\n"); } first = 0; memset(pic, ' ', sizeof(pic)); // 从外到内逐层填充 for (int layer = 0; layer <= n / 2; layer++) { char c = (layer % 2 == 0) ? ch1 : ch2; int top = layer; int bottom = n - 1 - layer; // 上边、下边、左边、右边四条线 for (int k = top; k <= bottom; k++) { pic[top][k] = c; pic[bottom][k] = c; pic[k][top] = c; pic[k][bottom] = c; } } // 四角挖空 pic[0][0] = ' '; pic[0][n - 1] = ' '; pic[n - 1][0] = ' '; pic[n - 1][n - 1] = ' '; // 输出,并去掉每行的尾随空格 for (int i = 0; i < n; i++) { int end = n - 1; while (end >= 0 && pic[i][end] == ' ') { end--; } for (int j = 0; j <= end; j++) { putchar(pic[i][j]); } putchar('\n'); } } return 0; }

3.2 为什么先存数组而不是边算边打印

直接 printf 当然也能画,但有一个问题绕不开:行尾空格。叠筐图的行首位置经常是空格,比如最外圈角点挖空后,首行和末行的第一个字符就是空格;而行尾偶尔也会是空格。如果边算边打印,遇到行尾空格时很难判断“这里到底要不要输出”。而先把结果写进二维数组,输出前单独做一次“找最后一个非空格字符”的处理,逻辑就完全可控。

初次写这道题的人最容易在格式上反复栽跟头。把格式处理和图案生成分开,是降低出错率的一条非常有效的经验。

3.3 裁剪行尾空格的原理

代码里的 while 循环从行尾往回找第一个不是空格的字符,记住它的下标 end,然后只输出 0 到 end 这一段。比如第一行原本是“空格 + B + 空格”,裁剪之后变成“空格 + B”。前导空格保留,末尾空格去掉。

前导空格一定要保留,这一点值得反复强调。它是图案的一部分,如果顺手裁掉,第一行的 B 就会贴到左边界,第二行却是从 B 开始的,整体图案就错位了。

4. 换一种更短的写法:距离公式直接定位

4.1 不逐层填充,直接算每个点

逐层填充理解起来直观,但要反复调整四条边的坐标。其实还有更数学化的写法:不关心“一圈一圈怎么填”,而是对每个坐标(i, j)独立计算它的层号,再根据层号奇偶一次性定位字符。这就是前面说的距离公式。

layer = min(i, j, n-1-i, n-1-j)

这种写法每个格子的字符只由它自己的位置决定,不依赖填充顺序。代码很短,也很容易迁移到其他图形题。

4.2 实现代码

这次不建完整的二维数组,只用一个临时行数组来存当前行,在换行前统一裁剪行尾空格:

#include <stdio.h> int min4(int a, int b, int c, int d) { int m = a; if (b < m) m = b; if (c < m) m = c; if (d < m) m = d; return m; } int main() { int n, first = 1; char ch1, ch2; char line[85]; while (scanf("%d %c %c", &n, &ch1, &ch2) == 3) { if (!first) { printf("\n"); } first = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { int layer = min4(i, j, n - 1 - i, n - 1 - j); if (layer == 0 && (i == 0 || i == n - 1) && (j == 0 || j == n - 1)) { line[j] = ' '; } else { line[j] = (layer % 2 == 0) ? ch1 : ch2; } } int end = n - 1; while (end >= 0 && line[end] == ' ') { end--; } for (int j = 0; j <= end; j++) { putchar(line[j]); } putchar('\n'); } } return 0; }

4.3 两版代码怎么选

逐层填充版本更贴近人眼观察画面的过程,适合刚接触图形题的人理解圈层概念;距离公式版本代码更简洁,定位逻辑更通用,适合想用“一行判断出一个字符”的人。两者的时间复杂度都是 O(n^2),n 最大不超过 79,性能上没有区别。

个人建议:先用逐层填充跑通思路,理解圈层结构之后,再试试距离公式写法。真正把两种思路都写一遍,对“图形题到底在考什么”的理解会深很多。

5. 调试实录:这些坑个个都要命

5.1 scanf 读字符的隐形坑

输入是“整数 + 空格 + 字符 + 空格 + 字符”,而 scanf 的%c不会自动跳过空白字符。我见过有人写scanf("%d%c%c", ...),结果第一个字符读进去的是数字后面的空格,真正的字符全错位了,整个图案看起来奇奇怪怪却怎么都找不出原因。

稳妥写法是scanf("%d %c %c", ...),两个%c前面都加空格,让格式化字符串跳过空白。另一个更稳的方案是scanf("%d %s %s", ...)读两个字符串,再取str[0]当字符用,这样连字符之间无空格的情况也能兼容。

5.2 多组输出之间的空行

要求是“用例和用例之间空一行”,不是“每个用例输出之后都加一个空行”。区别在于:第一个用例前面不能有空行,最后一个用例后面不能有多余空行。代码中用first做标记,只在非第一个用例之前先输出空行。用计数器caseNum判断caseNum > 1再输出空行也是一样的效果。

这个坑虽然简单,但在 OJ 上的杀伤力不小,会把 Accepted 变成 Presentation Error 或者 Wrong Answer。而且因为空行肉眼不易发现,排查起来还蛮费时间的。

5.3 行尾空格为什么算错

四角挖空之后,第一行实际内容是“空格 + 若干字符 + 空格”。末尾那个空格如果打出来,评测系统按字节比对时,会认为你的输出比标准答案多了一个字符。很多老牌 OJ 对这个题目的输出比较严格,不会把它当作可容忍的格式差异。所以行尾空格必须去掉,不是可选项。

处理方式就是我代码里的裁剪循环。再提醒一次:前导空格不能顺手裁掉,它是图案的一部分。

5.4 中心字符方向反了

如果你一开始就认定“中心是第二个字符”,然后从中心向外一圈一圈地填,遇到 n=5 这类数据多半会画出一个中心是第二个字符、最外圈也是第二个字符的图案。看起来还挺对称,特别容易让人忽略错误。

正确做法是从外向内核层号,layer 从 0 开始。最外圈永远用第一个字符,中心由layer % 2决定。自查技巧是:用 n=3 和 n=5 两个小数据分别验证。n=3 中心是第二个字符,n=5 中心是第一个字符,两个都通过基本就稳了。

5.5 数组大小与初始化

n 最大是奇数,顶多到 79。开85x85的数组肯定够。但要注意memset(pic, ' ', sizeof(pic))必须对整个数组做,不能只清一部分,否则没初始化到的区域会残留随机值。距离公式版本里临时行数组 line 每个位置都会被重新赋值,不需要再单独 memset,但要确认四角空格和普通字符位置都覆盖到了。

5.6 一个隐蔽小坑:循环边界

图形输出的另一个常见问题是整体少一行或者多一列,叠筐变成不对称的形状。这通常出在 layer 循环的范围上。逐层填充时,layer要从 0 循环到n/2,不能少,少了中心就填不上。四条边的填充范围是top到bottom,是闭区间。有人写成左闭右开,最后一行就漏了,这种错误在输出里经常表现为只差一行,容易被眼睛忽略。

为了看起来更方便,我把叠筐这道题最常踩的几个坑整理成了一个速查表:

现象可能原因对应解法
图案字符错乱scanf 的 %c 读入了空格两个 %c 前加空格,或用 %s 读字符串取首字符
多个用例之间空行不对在每个用例后都输出了空行用 first 标记,只在非首个用例前输出空行
行尾多出空格四角空格没有在输出前裁剪每行找到最后一个非空格下标,只输出到该位置
中心字符不对从内向外填充时固定中心为第二个字符从外向内核层号,用 layer % 2 决定字符
图案少了一圈layer 循环范围没走到 n/2确保循环是for (layer = 0; layer <= n / 2; layer++)
行首字符跑到左边界把前导空格也裁剪掉了只处理行尾空格,保留行首空格

6. 从叠筐延伸出去的通用思路

6.1 图形输出题的四步方法论

叠筐这类“图形输出题”,在 OJ 上是一大类,基本套路四步走。

第一步,观察规律。把图案拆成行、列、层,或者用某种距离公式描述,先弄清楚每个字符和坐标的关系。第二步,选择存储方式。要不要开二维数组,还是只存一行,取决于后面是否需要统一处理格式。第三步,确定填充顺序。可以逐层填,可以按坐标单独算,也可以从中心向外扩展。第四步,处理边界和格式。角点、行尾空格、用例间隔空行,这些细节决定了最终能不能 AC。

四步里第四步最容易被忽视,因为题解博客很少把 WA 的过程讲给你听,只有自己真提交过才知道疼。

6.2 类似的图形题都能怎么做

叠筐问题的思路可以平移很多地方。打印菱形,本质是曼哈顿距离阈值判断;螺旋矩阵也就是蛇形填数,本质是按层或者按方向向量填;数字回字矩阵,本质就是最外圈填 1、第二圈填 2,跟叠筐的圈层交替几乎一模一样;沙漏图形,同样可以用距离公式或分层判断。

一旦习惯了“用坐标关系描述几何图案”,这类题基本就通吃了。叠筐这种题考察的并不是某个算法,而是你能否把眼睛看到的规律,翻译成一个嵌套循环或者一行判断条件。这个能力在以后写界面布局、做表格样式、处理二维数据的时候都用得上。

我的习惯是每写完一个图形题,在代码旁边写两行注释:这个图形用什么公式描述、边界条件是什么。时间长了,图形题的出错率会明显降下来。叠筐这道题虽然基础,但它把“找规律、写循环、抠格式”这一整串动作完整地练了一遍,作为入门阶段的模拟题,非常值得静下心来多写几遍。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询