1. 蛇形矩阵到底在考什么
蛇形矩阵这个题,在编程练习平台里算是很经典的一道二维数组模拟题。我第一次碰到它的时候,觉得不就是把数字按顺序填进去嘛,能有多难。结果真上手写的时候,边界条件把我折腾得够呛。后来刷的题多了,回头再看这道题,才发现它表面上考的是数组操作,实际上考的是你对边界控制和方向切换的理解。
先把题目说清楚。蛇形矩阵通常指的是这样一个东西:给定一个正整数 n,生成一个 n 行 n 列的方阵,数字从 1 开始,按照蛇形走位依次填入。所谓蛇形走位,就是第一行从左到右填,到了行尾之后往下走一格,第二行从右到左填,到了行首再往下走一格,第三行又从左到右,如此反复直到填满整个矩阵。
举个例子,n=4 的时候,结果是这样的:
1 2 3 4 8 7 6 5 9 10 11 12 16 15 14 13看起来很简单对吧?但如果你真的动手写过,就会知道这里面有几个很容易翻车的地方。比如行末和行首的转向判断、奇偶行的处理差异、循环终止条件的设置,每一个细节没处理好,输出就会乱掉。
这道题适合什么人看?我觉得两类人最需要认真琢磨。一类是刚学完数组和循环、想找个题练手的初学者,蛇形矩阵能帮你把二维数组的索引操作练得很扎实。另一类是准备面试的人,这类模拟题在笔试环节出现频率不低,它不考你什么高级算法,就考你能不能把逻辑理清楚、把代码写对,这恰恰是面试官想看的底层能力。
接下来我会从最直观的解法讲起,然后逐步分析几种不同的实现思路,把每一步为什么这么做都掰开揉碎说清楚。中间还会穿插我自己踩过的坑和调试技巧,争取让你看完就能自己写出来,而且能举一反三。
2. 从手动填数到代码实现的思维转换
2.1 用最笨的办法先理解规律
在写代码之前,我建议你先拿张纸,手动把 n=5 的蛇形矩阵画出来。别觉得这是浪费时间,我见过太多人上来就敲代码,结果写到一半发现规律没搞明白,又回头重新推。
n=5 的蛇形矩阵长这样:
1 2 3 4 5 10 9 8 7 6 11 12 13 14 15 20 19 18 17 16 21 22 23 24 25你手动填一遍就会发现几个规律。第一,数字是连续递增的,从 1 到 n²,一个不多一个不少。第二,每一行的方向是交替的,奇数行(第1、3、5行)从左往右,偶数行(第2、4行)从右往左。第三,行与行之间的衔接点很关键——第一行填到最右边之后,下一个数字在第二行的最右边;第二行填到最左边之后,下一个数字在第三行的最左边。
这三个规律看起来是废话,但它们直接决定了你代码的结构。很多人写的时候只盯着“怎么填数字”,忽略了“什么时候换行、换行之后从哪边开始”这个核心问题。
2.2 方向切换的本质是什么
我们把问题抽象一下。你有一个 n×n 的二维数组,有一个当前数字 num 从 1 开始,有一个当前位置 (i, j)。每一步你要做的是:把 num 填到 (i, j),然后 num 加一,然后决定下一步往哪走。
在蛇形矩阵里,方向只有两种:向右或者向左。但什么时候切换方向?答案是:当你走到当前行的尽头时。
这里就引出一个关键判断:什么叫“走到尽头”?对于从左往右的行,尽头是 j == n-1;对于从右往左的行,尽头是 j == 0。一旦到达尽头,你就需要换到下一行,同时把方向反过来。
用伪代码表示就是:
i = 0, j = 0, num = 1 direction = right while num <= n * n: matrix[i][j] = num num += 1 if direction == right: if j == n - 1: i += 1 direction = left else: j += 1 else: if j == 0: i += 1 direction = right else: j -= 1这段伪代码的逻辑非常清晰,但它有一个隐藏的问题:当 i 增加到 n 的时候,循环其实已经该结束了,但因为 num 的条件控制,不会越界访问。不过如果你在换行的时候没有做好判断,就可能在最后一行之后还试图访问 matrix[n][...],导致数组越界。
2.3 为什么我不推荐用方向数组
有些教程会教你用方向数组来做,类似这样:
directions = [(0, 1), (0, -1)] # 右、左然后用一个索引来控制当前方向。这种做法在更复杂的矩阵题(比如螺旋矩阵)里确实好用,但对于蛇形矩阵来说,我觉得有点杀鸡用牛刀。蛇形矩阵只有两个方向,而且切换规则非常固定,直接用 if-else 判断反而更直观、更不容易出错。
当然,如果你是在练习阶段,想刻意训练自己用方向数组的思维,那也无可厚非。但如果是面试场景,代码的可读性很重要,面试官更想看到你清晰地表达逻辑,而不是炫技。
3. 三种实现方案的对比与选择
3.1 逐行填充法:最直观的写法
逐行填充法的思路是:外层循环遍历每一行,内层循环遍历该行的每一列,根据行号的奇偶性决定填充方向。
def snake_matrix(n): matrix = [[0] * n for _ in range(n)] num = 1 for i in range(n): if i % 2 == 0: # 偶数行,从左到右 for j in range(n): matrix[i][j] = num num += 1 else: # 奇数行,从右到左 for j in range(n - 1, -1, -1): matrix[i][j] = num num += 1 return matrix这段代码短小精悍,逻辑一目了然。它的时间复杂度是 O(n²),因为要填充 n² 个位置;空间复杂度也是 O(n²),因为要存储整个矩阵。对于这道题来说,这已经是最优的复杂度了,因为输出本身就需要 n² 个元素。
我特别喜欢这种写法的原因是:它把“行”和“列”的关系处理得很干净。你不需要维护额外的方向变量,也不需要担心边界切换的问题,行号的奇偶性天然就决定了方向。
但这种方法有一个局限:它只适用于规则的行交替蛇形矩阵。如果题目变成“之字形遍历”或者“对角线蛇形”,这种写法就不适用了,你得回到更通用的模拟思路。
3.2 模拟行走法:更通用的思路
模拟行走法就是我前面伪代码描述的那种方式:维护当前位置和当前方向,一步一步地走,走到边界就转向。
def snake_matrix_simulate(n): matrix = [[0] * n for _ in range(n)] i, j, num = 0, 0, 1 direction = 1 # 1 表示向右,-1 表示向左 while num <= n * n: matrix[i][j] = num num += 1 if direction == 1: if j == n - 1: i += 1 direction = -1 else: j += 1 else: if j == 0: i += 1 direction = 1 else: j -= 1 return matrix这种写法的好处是通用性强。如果你把方向切换的条件改一改,就能变成螺旋矩阵、对角线遍历等其他变体。缺点是代码稍微长一点,而且需要小心处理 i 和 j 的越界问题。
我在实际写的时候,习惯在 while 循环里加一个判断:如果 i >= n,直接 break。虽然理论上 num 的条件会先触发,但多加一层保险总没错。特别是在面试的时候,面试官很可能会问你“如果 n=0 或者 n=负数怎么办”,这时候你就需要提前处理这些边界情况。
3.3 数学公式法:看起来高级但没必要
还有一种思路是找数学公式。蛇形矩阵中每个位置 (i, j) 的值其实可以用公式直接算出来,不需要模拟填充。
对于偶数行 i(0-indexed),matrix[i][j] = i * n + j + 1。 对于奇数行 i,matrix[i][j] = i * n + (n - j)。
def snake_matrix_formula(n): matrix = [] for i in range(n): if i % 2 == 0: row = [i * n + j + 1 for j in range(n)] else: row = [i * n + (n - j) for j in range(n)] matrix.append(row) return matrix这种写法确实很优雅,一行代码就能生成一行数据。但说实话,在实际面试中我不太推荐主动写这种解法。原因有两个:第一,它需要你推导公式,推导过程容易出错;第二,面试官可能觉得你在背题,而不是在展示解题思维。
当然,如果你能清晰地解释公式的推导过程,那用公式法反而能体现你的数学功底。关键是要能说清楚“为什么奇数行的公式是这样的”,而不是直接甩出一个公式。
3.4 三种方案的对比
| 方案 | 代码长度 | 可读性 | 通用性 | 推荐场景 |
|---|---|---|---|---|
| 逐行填充法 | 短 | 高 | 低 | 面试快速手写 |
| 模拟行走法 | 中 | 中 | 高 | 需要扩展到其他变体 |
| 数学公式法 | 最短 | 中 | 低 | 追求代码简洁 |
我的建议是:面试的时候优先写逐行填充法,因为它最不容易出错,而且面试官一眼就能看懂你的逻辑。如果面试官追问“能不能用其他方法”,你再展示模拟行走法,说明你理解更通用的解题思路。
4. 那些年我踩过的边界坑
4.1 行末转向时忘记换行
这是我第一次写蛇形矩阵时犯的错。当时我的代码是这样的:
if direction == 1: if j == n - 1: direction = -1 # 只切换了方向,忘记 i += 1 else: j += 1结果就是数字一直在第一行来回填,把前面的数字覆盖掉了。调试的时候打印出来一看,第一行变成了 [5, 4, 3, 2, 1],后面的行全是 0。
这个错误的根本原因是我把“转向”和“换行”当成了两件事,但实际上在蛇形矩阵里,转向和换行是同时发生的。你走到行末的那一刻,既要改变方向,也要进入下一行。
提示:每次写边界判断的时候,问自己一句“这一步之后,下一个数字应该填在哪里”,如果答案不明确,说明你的逻辑有漏洞。
4.2 奇数行和偶数行的终止条件搞反
第二个坑是奇数行从右往左填的时候,终止条件应该是 j >= 0,而不是 j > 0。我一开始写成了:
for j in range(n - 1, 0, -1): # 错误:漏掉了 j=0这样会导致每一奇数行的第一个位置(最左边)没有被填充,输出结果里会出现 0。
正确的写法是range(n - 1, -1, -1),注意最后一个参数是 -1,表示步长为负,这样才能遍历到 0。
这个坑看起来很低级,但在手写代码的时候特别容易犯。因为 Python 的 range 函数是左闭右开的,range(n-1, -1, -1) 生成的是 n-1, n-2, ..., 1, 0,刚好覆盖所有列。如果你写成 range(n-1, 0, -1),生成的是 n-1, ..., 1,少了 0。
4.3 n=1 的特殊情况
当 n=1 的时候,蛇形矩阵就是 [[1]]。这个情况本身不复杂,但如果你在代码里做了“奇数行从右往左”的处理,而 n=1 时只有一行(第0行,偶数行),那其实不会触发奇数行逻辑。但如果你用模拟行走法,就要注意 while 循环的条件。
我见过有人在模拟行走法里这样写:
while i < n and j < n:这个条件在 n=1 的时候,第一轮 i=0, j=0 满足条件,填入 1 之后,j 变成 1(因为 direction=1 且 j != n-1 不成立,实际上 j==n-1==0,所以会触发 i+=1, direction=-1),然后 i 变成 1,循环条件 i < n 不满足,退出。结果是正确的。
但如果你的条件写成while i < n and j < n and num <= n*n,那也没问题。关键是不要写成while i <= n and j <= n,那样会多走一轮,导致数组越界。
4.4 输出格式的坑
有些题目要求输出的时候每个数字占固定宽度,比如用%3d或者{:3d}格式化。如果你直接 print 列表,输出会带方括号和逗号,不符合题目要求。
我一般会这样处理输出:
for row in matrix: print(' '.join(f'{x:3d}' for x in row))这样每个数字占3个字符宽度,右对齐,列与列之间用空格分隔,看起来整齐美观。如果 n 比较大(比如 n=10),数字可能超过3位,那就需要动态计算宽度,比如用len(str(n*n))来确定。
5. 从蛇形矩阵延伸出去的思考
5.1 螺旋矩阵和蛇形矩阵的关系
蛇形矩阵是“行交替方向”,螺旋矩阵是“沿着外圈顺时针或逆时针走”。两者都属于矩阵模拟题,但螺旋矩阵的边界控制更复杂,需要维护上下左右四个边界。
如果你能把蛇形矩阵写熟练,再去写螺旋矩阵会轻松很多。因为螺旋矩阵的核心也是“走到边界就转向”,只不过它有四个方向而不是两个。
我建议的练习顺序是:先写蛇形矩阵,再写对角线遍历,最后写螺旋矩阵。这三个题覆盖了矩阵模拟的主要模式,写完你对二维数组的掌控力会有一个明显的提升。
5.2 蛇形矩阵的变体:之字形遍历
之字形遍历(Zigzag Traversal)是蛇形矩阵的升级版。它不再是整行整行地填,而是沿着对角线走。比如对于矩阵:
1 2 3 4 5 6 7 8 9之字形遍历的输出是:1, 2, 4, 7, 5, 3, 6, 8, 9。
这种题目的边界控制更复杂,因为你要判断什么时候往上走、什么时候往下走、什么时候换对角线。但核心思路还是一样的:维护当前位置和方向,走到边界就调整。
5.3 实际工程中哪里会用到这些
你可能会问,蛇形矩阵这种题在实际工作中有用吗?说实话,直接用到的地方不多。但它的训练价值在于:让你习惯处理二维数组的索引和边界。
在图像处理、游戏开发、数据分析等领域,二维数组的遍历是家常便饭。比如图像旋转、卷积操作、矩阵运算,都需要你对索引有精准的控制。蛇形矩阵这种题,就是帮你把这种控制力练到肌肉记忆里。
另外,这类题在面试中的出现频率确实不低。我统计过一些公司的笔试题目,矩阵模拟题的出现概率大概在15%左右,其中蛇形矩阵和螺旋矩阵是最常见的两种。所以花时间把这类题吃透,投入产出比是很高的。
6. 调试蛇形矩阵的实用技巧
6.1 用小规模数据验证
我调试这类题的时候,从来不会一上来就用 n=10 去测。我会先用 n=1、n=2、n=3 这种小规模数据跑一遍,确认基本逻辑没问题,再逐步增大 n。
n=1 的输出应该是 [[1]]。 n=2 的输出应该是 [[1, 2], [4, 3]]。 n=3 的输出应该是 [[1, 2, 3], [6, 5, 4], [7, 8, 9]]。
如果这三个都对了,那基本逻辑就没问题。如果 n=3 错了,你就很容易定位到是哪一行的逻辑出了问题。
6.2 打印中间状态
在模拟行走法里,我习惯在每次填充之后打印当前的位置和方向:
print(f"填入 {num-1} 到 ({i}, {j}), 方向={direction}")这样你能清楚地看到每一步的走向,一旦发现方向切换的时机不对,马上就能定位到是哪一行代码的问题。
6.3 用断言做自检
如果你是在本地写代码,可以加一些断言来验证结果的正确性:
flat = [x for row in matrix for x in row] assert sorted(flat) == list(range(1, n*n+1)), "数字不连续或有重复"这个断言能帮你快速发现“数字覆盖”或“数字遗漏”的问题。如果断言失败,说明你的填充逻辑有漏洞。
6.4 常见错误速查表
| 错误现象 | 可能原因 | 排查方向 |
|---|---|---|
| 第一行数字倒序 | 方向初始值搞反 | 检查 direction 的初始值 |
| 某一行全是0 | 循环范围不对 | 检查 range 的起止参数 |
| 数字重复 | 换行时没有更新 i | 检查行末转向逻辑 |
| 数组越界 | 循环终止条件太宽松 | 检查 while 条件或 for 范围 |
| 最后一行缺失 | 循环提前退出 | 检查 num <= n*n 的条件 |
这张表是我自己总结的,每次调试的时候对照着看,能省不少时间。
7. 我的个人经验总结
写了这么多,最后分享几点我在反复练习蛇形矩阵过程中积累的心得。
第一,先画图再写代码。这个习惯看起来笨,但真的能帮你省很多调试时间。我现在遇到任何矩阵题,都会先在纸上画一个 3×3 或 4×4 的格子,手动填一遍,把规律看清楚再动手。
第二,边界条件单独处理。不要试图用一套逻辑覆盖所有情况,该用 if-else 就用 if-else。代码长一点没关系,可读性和正确性更重要。
第三,多写几种解法。同一道题,用逐行填充法写一遍,再用模拟行走法写一遍,你对题目的理解会完全不一样。面试的时候,如果面试官问“还有没有其他思路”,你就能从容应对。
第四,别怕犯错。我写蛇形矩阵至少错了五六次,每次都是不同的边界问题。但正是这些错误让我对二维数组的索引控制越来越熟练。现在再写类似的题,基本上一次就能过。
第五,把调试过程记录下来。我现在有一个习惯,每次调试超过10分钟的题,都会把错误原因和修复方法记在笔记里。时间长了,这份笔记就成了我自己的“错题本”,复习的时候特别有用。
蛇形矩阵这道题,说难不难,说简单也不简单。它不需要你懂什么高级算法,但需要你有清晰的逻辑和细致的边界控制能力。如果你能把这道题吃透,再去看其他矩阵模拟题,会发现它们都是同一个套路。希望这篇内容能帮你少走一些弯路,顺利把这道题拿下。