
1. 题目考点
这道题主要考:
字符串拼接、列表存储每一行、join() 连接字符、center() 居中填充、循环构造规律、上下对称图形打印。
它不是难在语法,而是难在看出图案规律。
2. 审题
题目给你一个整数 size,让你生成一个字母 rangoli 图案。
例如 size = 3 时,用到字母:
a b c
但是图案从外到内是:
----c----
--c-b-c--
c-b-a-b-c
--c-b-c--
----c----
所以我们要注意几个信息:
第一,最外层是第 size 个字母。size = 3,最外层是 c。size = 5,最外层是 e。
第二,中间一定是 a。
第三,图案上下对称。
也就是说,只要我们能生成上半部分加中间行,下半部分可以直接倒过来复用。
第四,每一行左右也对称。
例如:
c-b-a-b-c
左边是:
c-b
中间是:
a
右边是:
b-c
3. 思路提示
先不要急着写代码。我们先找规律。
对于 size = 3,核心内容其实是这三行:
c
c-b-c
c-b-a-b-c
然后再把它们居中补 -,最后再镜像出下半部分。
所以做题方向是:
第一步,准备字母表:
letters = "abcdefghijklmnopqrstuvwxyz"
第二步,生成从顶部到中间的每一行。
对于 size = 3:
i = 2 -> c
i = 1 -> c-b-c
i = 0 -> c-b-a-b-c
这里的 i 可以理解成“这一行中间最小的字母下标”。
第三步,把这一行的字母用 - 连接起来。
比如:
["c", "b", "a", "b", "c"]
变成:
c-b-a-b-c
第四步,把这一行放到固定宽度中间,两边用 - 补齐。
第五步,生成下半部分。
因为图案上下对称,所以:
上半部分 + 上半部分去掉最后一行后倒序
4. 完整设计思路
第一步:确定总宽度
中间行最长。
以 size = 3 为例:
c-b-a-b-c
这里有:
字母数量:2 * size - 1 = 5
横杠数量:2 * size - 2 = 4
总宽度:5 + 4 = 9
所以总宽度公式是:
width = 4 * size - 3
例如:
size = 3 -> width = 9
size = 5 -> width = 17
第二步:生成每一行的字母
假设:
letters = "abcdefghijklmnopqrstuvwxyz"
size = 3
我们需要用到:
letters[0] = a
letters[1] = b
letters[2] = c
从顶部到中间,i 从 size - 1 递减到 0:
for i in range(size - 1, -1, -1):
当 size = 3 时:
i = 2
i = 1
i = 0
对应三行:
i = 2 -> c
i = 1 -> c-b-c
i = 0 -> c-b-a-b-c
生成一行时,可以分成左半部分和右半部分。
比如 i = 0 时:
左半部分:c b
中间及右半部分:a b c
合起来:c b a b c
第三步:用 join() 加横杠
如果我们有一个列表:
row_letters = ["c", "b", "a", "b", "c"]
可以用:
"-".join(row_letters)
得到:
c-b-a-b-c
第四步:用 center() 居中补横杠
例如:
"c".center(9, "-")
结果是:
----c----
也就是说,center(总宽度, 填充字符) 会让字符串居中,不够的位置用指定字符补齐。
这里我们要用:
row.center(width, "-")
5. 代码实现
如果题目模板要求你写 print_rangoli(size),可以这样写:
def print_rangoli(size):
letters = "abcdefghijklmnopqrstuvwxyz"
lines = []
width = 4 * size - 3
for i in range(size - 1, -1, -1):
row_letters = []
# 左半部分:从最大的字母往当前字母前一个走
for j in range(size - 1, i, -1):
row_letters.append(letters[j])
# 中间和右半部分:从当前字母往最大的字母走
for j in range(i, size):
row_letters.append(letters[j])
row = "-".join(row_letters)
row = row.center(width, "-")
lines.append(row)
# 下半部分是上半部分去掉中间行之后的倒序
bottom_lines = lines[-2::-1]
all_lines = lines + bottom_lines
print("\n".join(all_lines))
如果你的平台要求函数 return 一个字符串,而不是直接 print,可以写成:
def rangoli(size):
letters = "abcdefghijklmnopqrstuvwxyz"
lines = []
width = 4 * size - 3
for i in range(size - 1, -1, -1):
row_letters = []
for j in range(size - 1, i, -1):
row_letters.append(letters[j])
for j in range(i, size):
row_letters.append(letters[j])
row = "-".join(row_letters)
row = row.center(width, "-")
lines.append(row)
bottom_lines = lines[-2::-1]
all_lines = lines + bottom_lines
return "\n".join(all_lines)
如果题目最后有类似:
if __name__ == '__main__':
n = int(input())
print_rangoli(n)
你就保留 print_rangoli(size) 版本即可。
6. 运行演示:以 size = 3 为例
首先:
letters = "abcdefghijklmnopqrstuvwxyz"
width = 4 * 3 - 3
所以:
width = 9
循环从:
i = 2, 1, 0
开始。
当 i = 2 时:
row_letters = ["c"]
连接后:
c
居中后:
----c----
当 i = 1 时:
row_letters = ["c", "b", "c"]
连接后:
c-b-c
居中后:
--c-b-c--
当 i = 0 时:
row_letters = ["c", "b", "a", "b", "c"]
连接后:
c-b-a-b-c
本身已经是宽度 9,所以不需要补横杠:
c-b-a-b-c
此时 lines 是:
[
"----c----",
"--c-b-c--",
"c-b-a-b-c"
]
然后下半部分是:
lines[-2::-1]
结果是:
[
"--c-b-c--",
"----c----"
]
最后合起来:
----c----
--c-b-c--
c-b-a-b-c
--c-b-c--
----c----
7. 方法总结
这一类图形题,不要一开始就写代码。先按这个顺序思考:
第一,找最大宽度。
因为图案通常需要对齐,必须先知道每一行应该占多宽。
第二,找“核心行内容”。
这里核心不是横杠,而是字母变化:
c
c-b-c
c-b-a-b-c
第三,判断是否有对称性。
这道题上下对称,所以只需要生成上半部分和中间行。
第四,把每一行作为字符串放进列表。
最后用:
"\n".join(lines)
一次性合成最终结果。
第五,图形题常用工具包括:
join()
center()
append()
range()
这道题最关键的是:
先生成每行字母,再用 - 连接,再居中补 -,最后上下镜像。
8. 同类型小练习
请你尝试打印下面这种数字菱形。
输入:
3
输出:
--3--
-323-
32123
-323-
--3--
提示:
这道题和 alphabet rangoli 很像,只是把字母换成了数字。
思考方向:
size = 3
第一行:3
第二行:3 2 3
第三行:3 2 1 2 3
仍然可以先生成上半部分,再镜像下半部分。
bottom_lines = lines[-2::-1]
bottom_lines = lines[-2::-1],这个代码没有看懂
这行代码:
bottom_lines = lines[-2::-1]
核心是在做一件事:从 lines 里取出下半部分,并且倒着取,避免重复中间最长的一行。
可以先把它拆成三部分看:
lines[start : stop : step]
对应到你的代码就是:
lines[-2 : : -1]
含义是:
| 部分 | 写法 | 含义 |
|---|---|---|
| start | -2 | 从倒数第二个元素开始 |
| stop | 空着 | 一直取到列表开头 |
| step | -1 | 倒着取 |
所以:
lines[-2::-1]
意思就是:
从倒数第二行开始,倒着一路取到第一行。
用你的 rangoli 举例。假设 size = 3,前面生成出来的 lines 是:
lines = [
"----c----",
"--c-b-c--",
"c-b-a-b-c"
]
这三行其实是:
----c----
--c-b-c--
c-b-a-b-c
这只是上半部分加中间行。
完整图案还需要下半部分:
--c-b-c--
----c----
这个下半部分正好就是把前面的上半部分倒过来。
但是注意,不能从最后一行开始倒着取。
如果写成:
lines[-1::-1]
结果是:
[
"c-b-a-b-c",
"--c-b-c--",
"----c----"
]
这样会把中间最长的一行 "c-b-a-b-c" 重复一次。
所以要从倒数第二行开始:
lines[-2::-1]
结果是:
[
"--c-b-c--",
"----c----"
]
正好就是下半部分。
所以这两行:
bottom_lines = lines[-2::-1]
all_lines = lines + bottom_lines
等价于:
bottom_lines = [
"--c-b-c--",
"----c----"
]
all_lines = [
"----c----",
"--c-b-c--",
"c-b-a-b-c",
"--c-b-c--",
"----c----"
]
最后再:
print("\n".join(all_lines))
输出完整图案。
对初学者来说,这行代码可以先写成更容易理解的循环版本:
bottom_lines = []
for i in range(len(lines) - 2, -1, -1):
bottom_lines.append(lines[i])
这段和下面这一行效果一样:
bottom_lines = lines[-2::-1]
逐步解释:
range(len(lines) - 2, -1, -1)
表示:
从倒数第二行的下标开始,倒着走,一直走到第 0 行。
这一句的关键记法是:
列表[-2::-1]
可以理解成:
不要最后一个,从倒数第二个开始,倒着取到开头。
在这道题里,最后一个就是中间最长的那一行。它已经在 lines 里面出现过了,所以生成下半部分时不能再重复它。
小练习:
nums = [10, 20, 30, 40, 50]
print(nums[-2::-1])
你可以先不要运行,自己判断它会输出什么。提示:从倒数第二个元素 40 开始,倒着取到开头。



