1. 题目背景与核心需求
这道来自天梯赛的L1-002题目看似简单,实则蕴含了编程初学者必须掌握的几项核心能力。作为从Hello World进阶后的第二道题目,它要求我们:
- 根据输入的字符总数N(N≤1000)和指定字符
- 打印出能使用最多字符的沙漏形状
- 最后输出剩余未使用的字符数
沙漏形状有严格定义:
- 由上下对称的两部分组成
- 每行字符数为奇数,相邻行差2个字符
- 所有行必须中心对齐
- 先递减到1个字符,再递增回原始数量
关键提示:当N不足7个字符时(最小沙漏需要7个字符:3-1-3),只能输出一个字符的沙漏并返回N-1
2. 数学规律解析
2.1 沙漏的数学本质
沙漏实际上是一个等差数列的对称结构。假设最大层数为k(中间1个字符的行为第1层),则:
- 上半部分:从(2k-1)个字符递减到1个字符,步长-2
- 下半部分:从3个字符递增到(2k-1)个字符,步长+2
总字符数公式为:
code复制总字符数 = 2*(1+3+...+(2k-1)) - 1 = 2*k² - 1
(减去1是因为中间的1个字符被重复计算了)
2.2 计算最大层数k
我们需要找到满足2k²-1 ≤ N的最大k值。可以通过以下步骤实现:
- 初始化k=1
- 循环计算2k²-1,直到结果超过N
- 最后k-1就是最大有效层数
例如N=17时:
- k=1: 2*1-1=1 ≤17
- k=2: 2*4-1=7 ≤17
- k=3: 2*9-1=17 ≤17
- k=4: 2*16-1=31 >17 → 最终k=3
3. C语言实现详解
3.1 完整代码实现
c复制#include <stdio.h>
#include <math.h>
int main() {
int N, k = 0;
char c;
scanf("%d %c", &N, &c);
// 计算最大层数k
while (2*(k+1)*(k+1)-1 <= N) {
k++;
}
// 打印上半部分
for (int i = k; i >= 1; i--) {
// 打印空格
for (int j = 0; j < k - i; j++) {
printf(" ");
}
// 打印字符
for (int j = 0; j < 2*i-1; j++) {
printf("%c", c);
}
printf("\n");
}
// 打印下半部分
for (int i = 2; i <= k; i++) {
// 打印空格
for (int j = 0; j < k - i; j++) {
printf(" ");
}
// 打印字符
for (int j = 0; j < 2*i-1; j++) {
printf("%c", c);
}
printf("\n");
}
// 输出剩余字符数
printf("%d", N - (2*k*k-1));
return 0;
}
3.2 关键点解析
-
层数计算优化:
- 使用
k+1预判,避免最后减1操作 - 比直接使用
k=1开始计算更高效
- 使用
-
中心对齐实现:
- 每行前置空格数 = k - 当前层数
- 例如k=3时:
- 第1行(5个字符):3-3=0空格
- 第2行(3个字符):3-2=1空格
- 第3行(1个字符):3-1=2空格
-
字符数控制:
- 上半部分字符数:2i-1(i从k递减到1)
- 下半部分字符数:2i-1(i从2递增到k)
4. C++实现对比
4.1 完整代码实现
cpp复制#include <iostream>
#include <cmath>
using namespace std;
int main() {
int N, k = 1;
char c;
cin >> N >> c;
// 计算最大层数k
while (2*k*k-1 <= N) {
k++;
}
k--;
// 打印上半部分
for (int i = k; i >= 1; i--) {
cout << string(k-i, ' ') << string(2*i-1, c) << endl;
}
// 打印下半部分
for (int i = 2; i <= k; i++) {
cout << string(k-i, ' ') << string(2*i-1, c) << endl;
}
// 输出剩余字符数
cout << N - (2*k*k-1);
return 0;
}
4.2 语言特性对比
-
输入输出差异:
- C使用
scanf/printf - C++使用
cin/cout和string构造
- C使用
-
字符串处理优化:
- C++的
string构造函数可以直接生成指定数量的字符 - 比C的循环打印更简洁
- C++的
-
层数计算差异:
- C++需要先
k++再k-- - 因为判断条件执行后k会多增1
- C++需要先
5. 常见问题与调试技巧
5.1 典型错误案例
-
层数计算错误:
- 错误:直接使用k=sqrt((N+1)/2)取整
- 问题:浮点数运算可能产生精度问题
- 正确:使用整数循环判断
-
空格数计算错误:
- 错误:认为所有行空格数相同
- 正确:每行空格数随层数变化
-
剩余字符数错误:
- 错误:忘记输出剩余数或计算公式错误
- 正确:N - (2kk-1)
5.2 调试建议
-
小规模测试:
- 先测试N=1(最小情况)
- 再测试N=7(最小完整沙漏)
- 最后测试边界值N=1000
-
打印中间变量:
- 输出计算得到的k值
- 输出总使用字符数2kk-1
-
可视化调试:
- 用'.'代替空格便于观察对齐
- 例如:
code复制
***** *** * *** *****
6. 算法优化思路
6.1 时间复杂度分析
当前算法:
- 层数计算:O(√N)
- 打印沙漏:O(k²) = O(N)
- 总体:O(N)
6.2 潜在优化方向
-
数学公式优化:
c复制k = (int)sqrt((N+1)/2); while (2*k*k-1 > N) k--;但需注意浮点数精度问题
-
并行打印:
- 上半部分和下半部分可以合并处理
- 减少循环次数
-
空间换时间:
- 预先计算所有行的字符串
- 然后一次性输出
7. 扩展思考
7.1 其他形状打印
掌握本题目后,可以尝试类似题目:
- 打印菱形(去掉下半部分)
- 打印空心沙漏(只保留边框)
- 打印数字沙漏(使用数字代替字符)
7.2 多语言实现
同样的逻辑可以用其他语言实现:
- Python利用字符串乘法更简洁
- Java需要注意输入输出效率
- Go语言可以利用defer特性
7.3 实际应用场景
这类题目训练的能力在实际开发中很有用:
- 日志信息的格式化输出
- 控制台UI的绘制
- 文本报表的生成
我在实际项目中就遇到过需要生成类似结构的文本报告的需求,当时这道题的训练经验直接派上了用场。特别是中心对齐的技巧,在处理不同长度的数据展示时非常实用。