很多同学跟我吐槽:Python 循环嵌套里,最难的不是 for,也不是 if,而是打印各种奇形怪状的字符图案。
比如这个数字金字塔:
1 123 12345 1234567 123456789
当初学这块的时候,你是不是也这样?
老师刚讲完,你点头如捣蒜:“懂了懂了,第一行 1 个,第二行 3 个,第三行 5 个……是奇数序列嘛!”
然后换一道题:
或者倒过来的金字塔,或者菱形的上半部分……
你立马懵了:这还是奇数序列吗?这规律怎么变了?
于是你开始疯狂记笔记:
左对齐是 i+1
右对齐是 n-i-1
金字塔是 2*i+1
菱形要拆两半……
结果就是:简单的题靠回忆,复杂的题靠瞎蒙,变形题直接放弃。
问题的根源在于:你一直在“记规律”,而不是“建模型”。
“记规律”是经验主义的产物,它脆弱、零散、不可迁移;而计算机科学需要的是确定性。只要你还在靠“眼力”去找规律,你就永远会被更复杂的图案绕晕。
今天,我要教你一个“机械推导法”。这个方法不需要你有敏锐的观察力,只需要你会基本的加减乘除。哪怕图案再复杂,你也能像解数学方程一样,一步步推导出正确的循环次数。
我们还是拿刚才那个数字金字塔举例。
第一步:拒绝“看”,开始“列”
不要试图用脑子去“感觉”它是奇数。拿出纸笔,列出一张表。
设总行数为 n=5,行号 i从 0 开始:
行号 i | 前导空格 | 有效字符数 |
|---|
0 | 4 | 1 |
1 | 3 | 3 |
2 | 2 | 5 |
3 | 1 | 7 |
4 | 0 | 9 |
空格数很好办:4 - i。
但有效字符数(1, 3, 5, 7, 9)和 i的关系,一眼真看不透。这时候怎么办?
第二步:乘除逼近法(核心心法)
当你看不出 i和目标数值的关系时,用“乘除逼近”:
这里,有效字符数明显大于 i,我们尝试用 乘法 逼近。
先试 i × 2:
行号 i | i × 2 | 有效字符数 |
|---|
0 | 0 | 1 |
1 | 2 | 3 |
2 | 4 | 5 |
3 | 6 | 7 |
4 | 8 | 9 |
关键转折点来了:
i × 2的结果是 0, 2, 4, 6, 8,而我们要的是 1, 3, 5, 7, 9。
两者只差了一个 1。
第三步:修正偏移,得到公式
既然 2i差点意思,那我们就补上那个差值。
由此,我们得到了精确的数学公式:
有效字符数=2×i+1
进阶实战:当内容也开始“调皮”
刚才我们只解决了“打印多少个字符”。那如果打印什么字符也变得不按常理出牌呢?
比如这道题,哪怕是有经验的程序员,第一眼也未必能立刻写出代码:
如果你靠“眼力”,可能会觉得乱:怎么一会儿一个字符,一会儿两个,内容还跳着长?
我们依然用老办法,先列表格(只看有效字符部分):
行号 i | 第 0 列 | 第 1 列 | 第 2 列 | 第 3 列 |
|---|
0 | 1 | | | |
1 | 1 | 3 | | |
2 | 1 | 3 | 5 | |
3 | 1 | 3 | 5 | 7 |
观察列号 j和内容的关系:
当 j=0时,值是 1
当 j=1时,值是 3
当 j=2时,值是 5
用我们的“乘除逼近法”:目标值比 j大,尝试 j × 2,得到 0, 2, 4。
距离目标 1, 3, 5只差了 1。
于是我们得到字符内容的公式:2 × j + 1。
对应的代码也就呼之欲出了:
n = 4for i in range(n):# 打印字符 for j in range(i + 1): # 第i行有i+1个字符 print(2 * j + 1, end="") print()
你看,再复杂的图案,只要拆成“行号 i”和“列号 j”两个维度,再用乘除逼近去磨,总能磨出那个确定的公式。
写在最后
以前你做图案题,是在背题;现在你学会了机械推导法,是在建模。
下次再遇到图案题,别急着找规律,先拿出这张表。你会发现,那些曾经让你头疼的星星和数字,不过是几个简单的数学公式在屏幕上跳舞罢了。
记住:在编程的世界里,感觉是不可靠的,公式是可靠的。