PTA L1-039 古风排版二维数组与文字竖排的艺术第一次看到古风排版题目时我盯着那个asa T st ih e tsi ce s的输出样例发了十分钟呆。作为习惯了从左到右阅读的现代人要理解这种从右向左、从上到下的文字排列方式确实需要一些思维转换。不过当我用二维数组模拟出整个过程后一切都变得清晰起来。1. 理解古风排版的本质古风排版的核心在于文字排列方向的改变。现代排版是水平方向优先而古代排版则是垂直方向优先。这种差异直接影响了我们处理字符串的方式。关键特征文字从右向左排列列顺序每列的文字从上向下排列行顺序最后一列可能不足N个字符时需要补空格举个例子对于输入Hello WorldN3应该这样思考H e l l o W o r l d我们需要把这个字符串按3行分割后从右向左排列原字符串分段 Hel lo Wor ld 竖排后 l oW lo H d2. 二维数组的结构设计用二维数组模拟这种排版关键在于确定数组的行列关系。这里有一个思维转换数组的行数 用户输入的N每列字符数数组的列数 ceil(字符串长度 / N)int len strlen(input_str); int cols len / rows; if (len % rows ! 0) { cols; // 不能整除时需要额外一列 }这个计算确保了即使字符串长度不是N的整数倍我们也有足够的空间存储所有字符最后一列不足的部分用空格填充。3. 填充算法的逆向思维最让初学者困惑的是填充顺序——为什么要从最后一列开始让我们用ASCII图示来说明假设输入是This is a test caseN4原始字符串索引 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 T h i s i s a t e s t c a s e我们需要创建一个4行x5列的数组因为18个字符/4行4.5→5列填充顺序如下列号: 4 3 2 1 0 --------------------- 行0 | a | s | a | | T | 行1 | s | t | | i | h | 行2 | e | | t | s | i | 行3 | | c | s | | e |对应的填充代码char grid[100][100] {0}; int index 0; for (int col cols - 1; col 0; col--) { for (int row 0; row rows; row) { if (input_str[index] ! \0) { grid[row][col] input_str[index]; } else { grid[row][col] ; // 不足部分补空格 } } }4. 调试技巧与常见陷阱在实现这个算法时我踩过几个典型的坑数组越界没有正确计算列数导致访问非法内存解决方法打印中间计算结果验证printf(字符串长度%d, 需要列数%d\n, len, cols);空格处理忘记处理最后一列不足时的空格补全关键检查if (input_str[index] ! \0) { // 正常赋值 } else { // 补空格 }填充顺序混淆误从左到右填充导致顺序错误记忆技巧想象真的在竹简上写字从最右侧开始调试时可以打印中间状态// 在填充循环内加入调试输出 printf(填充 col%d, row%d, char%c\n, col, row, grid[row][col]);5. 完整代码实现与优化结合上述分析这是经过优化的完整实现#include stdio.h #include string.h #define MAX_STR 1001 #define MAX_GRID 100 int main() { int rows; scanf(%d , rows); // 注意空格吸收换行 char input_str[MAX_STR]; fgets(input_str, MAX_STR, stdin); input_str[strcspn(input_str, \n)] \0; // 去除换行符 int len strlen(input_str); int cols len / rows; if (len % rows ! 0) cols; char grid[MAX_GRID][MAX_GRID] {0}; int index 0; // 从右向左填充列 for (int col cols - 1; col 0; col--) { for (int row 0; row rows; row) { grid[row][col] (index len) ? input_str[index] : ; } } // 输出结果 for (int row 0; row rows; row) { for (int col 0; col cols; col) { putchar(grid[row][col]); } putchar(\n); } return 0; }优化点使用fgets替代不安全的gets用三元运算符简化条件判断添加了数组大小宏定义提高可维护性更安全的换行符处理6. 可视化理解工具为了更直观地理解这个过程我设计了一个简单的文本可视化方法void visualize_filling(int rows, int cols, const char grid[][MAX_GRID]) { printf(\n填充过程可视化\n); for (int i 0; i rows; i) { for (int j 0; j cols; j) { printf(%2d:%-2c , i * cols j, grid[i][j]); } printf(\n); } }调用这个函数可以看到字符是如何被填入二维数组的对于调试特别有帮助。7. 性能分析与扩展思考虽然这个问题规模很小N100字符串1000但思考优化方案是很好的练习空间优化可以直接计算输出位置不需要二维数组公式输出字符位置 (cols - 1 - col) * rows row边界情况空字符串输入N1的情况变成简单的字符串反转N大于字符串长度的情况尝试实现这些优化是理解算法本质的好方法。比如无二维数组版本for (int row 0; row rows; row) { for (int col cols - 1; col 0; col--) { int index col * rows row; putchar(index len ? input_str[index] : ); } putchar(\n); }这种实现直接计算字符位置节省了O(N²)的空间但可读性稍差。在算法竞赛中这种优化往往很有价值。