
蛇形矩阵这四个字一出来刷过算法题的人脑子里大概率已经蹦出好几种画面可能是从小到大按行来回填充的弹簧状数字表也可能是一圈一圈往里绕的螺旋数组还可能是斜着走、碰到边界就转向的之字形扫矩阵。不同题目、不同教材里管它们都叫蛇形矩阵于是很多人初学时一头雾水我到底该按哪种练这期就把这件事彻底理清楚。我会把三种最常见的蛇形矩阵定义分别列出来讲透每种解法背后的方向控制和边界处理思路再给出可以直接抄走的代码。无论你是刚开始学二维数组准备笔试面试还是工作中偶尔需要处理矩阵扫描类逻辑这篇都能帮你省下不少折腾的时间。写这种题没有太多高深数学核心就一句话把“走格子”的规则想清楚代码自然就顺了。1. 初识蛇形矩阵先分清三种最常见的形态1.1 形态一按行蛇形填充这种形态在入门教材里最常见尤其是一些 C 语言习题册。规则很直白对于一个 n×n 的矩阵从第一行开始从左到右填 1 到 n第二行反过来从右到左填第三行再正过来如此往复。生成的数字排布就像蛇一样来回游走。举个例子n4 时的样子是1 2 3 4 8 7 6 5 9 10 11 12 16 15 14 13注意第二行和第四行是反向的其他行正向。这个形态的关键在于“行号决定方向”偶数行从左到右奇数行从右到左前提是你把行号从 0 开始数。这个判断看起来简单但很多人写的时候会搞混行号奇偶和方向之间的关系尤其是当矩阵不是从 1 开始编号或者从 0 开始计数时更容易绕进去。1.2 形态二同向螺旋矩阵另一个被频繁叫做“蛇形矩阵”的是螺旋矩阵严格说它应该叫 spiral matrix。规则是从左上角出发先向右走碰到矩阵右边界后向下再碰到下边界后向左再碰到左边界后向上然后在这个缩小的矩形框里继续走一圈一圈向中心逼近。n4 的螺旋填充结果长这样1 2 3 4 12 13 14 5 11 16 15 6 10 9 8 7可以看到数字绕了一圈又一圈最后停在中心附近。这种形态比按行蛇形难在边界处理因为四个方向都要管每走完一条边就要更新一次边界。1.3 形态三对角线之字形遍历第三种常见形态是按对角线方向交替扫描经常出现在面试题里LeetCode 498 就是典型代表。它不要求填充而是要求按照斜线方向把矩阵所有元素读出来。对于下面这个矩阵1 2 3 4 5 6 7 8 9之字形遍历的输出是1 2 4 7 5 3 6 8 9这个规则是从左上角出发先沿右上方向斜着走走到上边界或右边界就转向左下方向碰到下边界或左边界再转向右上。因为轨迹像蛇一样蜿蜒所以也被不少人归入蛇形矩阵的范畴。这三种形态看起来各不相同但本质上是同一个问题在二维数组里按照某种规则从一个格子走到下一个格子。只要把方向控制和边界判定想明白任何变体都能套同一套方法论。2. 核心思路拆解把“走格子”变成可执行的规则2.1 用方向数组统一处理四个方向写蛇形矩阵最笨的办法是每走一步就写一个 if 判断“当前该往哪走”。四个方向就得写四段逻辑中间只要漏一个条件基本就是死循环或者越界。我见过太多人在这一步卡住。更好的做法是把方向抽象成二维坐标偏移。在矩阵里上、下、左、右四个方向分别对应行坐标和列坐标的变化上行坐标 -1列坐标 0 下行坐标 1列坐标 0 左行坐标 0列坐标 -1 右行坐标 0列坐标 1这四条偏移可以放进一个数组里# 顺序按“右、下、左、上”排列正好对应螺旋的前进方向 DIRS [(0, 1), (1, 0), (0, -1), (-1, 0)]然后维护一个方向指针走完一条边就让它加一再对 4 取模。这样一来螺旋矩阵的代码结构会变得非常干净方向决定偏移量边界决定什么时候转向。两者配合就能把复杂的行走过程简化成“循环 转向”两步。按行蛇形填充也可以用这个思路但更简单——它只有一个方向在左右切换所以用步长 1 和 -1 表达正反方向就够用了。2.2 边界收缩策略螺旋矩阵的“缩圈”思想螺旋矩阵最精妙的地方在于边界收缩。你可以在每一轮走完一条边后把对应的边界往内收一格从左走到右结束上边界上移一行表示“顶部这一行已经填完了”。从上走到下结束右边界左移一列。从右走到左结束下边界上移一行。从下走到上结束左边界右移一列。这个策略的价值在于你不需要在每一步去计算“前面有没有填过”。只要保证当前坐标始终在 left、right、top、bottom 四维边界围成的矩形内就永远不可能踩到已经填过的地方。我在实际写题时喜欢用一个计数变量来判断循环终止而不是用边界变量之间的大小关系。比如待填数字从 1 数到 n×n每填一个数字计数加一。这样写有一个隐含的好处当 n 是奇数时最中间那一个格子不会因为边界交叉而被漏掉。很多人用 top bottom 作为循环条件在奇数 n 的场景下偶尔会漏中心点改用计数判断则绝对安全。2.3 为什么很多新手在状态翻转上栽跟头之字形遍历的难点不在方向数组而在于“碰到边界之后怎么调整位置”。一个典型的错误是走到角落时只转向不调整坐标结果下一步走越界。我建议把这种问题分成两个阶段看。第一阶段先尝试按当前方向走一步如果新坐标仍然在矩阵范围内那就直接走过去。第二阶段如果新坐标越界了先不要急着转向而是先处理一个“防越界修正”把坐标移动到正确的新起点再翻转方向。这个“先试探后修正”的模式几乎可以通吃所有矩阵遍历类问题我后面给的代码也是按这个思路写的。3. 三种主流实现方案完整代码与逐行解读3.1 按行蛇形填充最基础的双循环实现这个形态写起来最轻松重点在行号奇偶切换方向。我直接给出代码def snake_fill(n: int): matrix [[0] * n for _ in range(n)] cur 1 for i in range(n): if i % 2 0: # 偶数行从左到右 for j in range(n): matrix[i][j] cur cur 1 else: # 奇数行从右到左 for j in range(n - 1, -1, -1): matrix[i][j] cur cur 1 return matrix这段代码的核心只有一个判断i % 2 0。为方便理解我把行号当作从 0 开始这样第一行就是偶数行正着填第二行是奇数行反着填。你也可以把正反两段合并成带步长的循环更简洁def snake_fill(n: int): matrix [[0] * n for _ in range(n)] cur 1 for i in range(n): step 1 if i % 2 0 else -1 start 0 if step 1 else n - 1 end n - 1 if step 1 else 0 for j in range(start, end step, step): matrix[i][j] cur cur 1 return matrix很多初学者会把偶数行和奇数行的定义搞反因为题目里如果写“第一行从左到右”那么从 1 开始的自然行号会和从 0 开始的数组下标差 1。我的建议很朴素统一用下标思考第一行就是第 0 行这样和代码一一对应不给自己制造混乱。如果想改成“按列蛇形填充”只要把 i 和 j 的角色互换外层循环变成列内层循环根据列号奇偶决定从哪一行开始填。这个变体在很多图形图像扫描场景会用到我放在后面讲变体时再展开。3.2 螺旋矩阵边界收缩法的标准实现螺旋矩阵的代码我很推荐背下来因为它的思想通用性极强。边界收缩版本的实现如下def spiral_fill(n: int): matrix [[0] * n for _ in range(n)] top, bottom, left, right 0, n - 1, 0, n - 1 cur 1 while cur n * n: # 1. 从左到右填充顶部这一行 for j in range(left, right 1): matrix[top][j] cur cur 1 top 1 # 2. 从上到下填充右侧这一列 for i in range(top, bottom 1): matrix[i][right] cur cur 1 right - 1 # 3. 从右到左填充底部这一行 for j in range(right, left - 1, -1): matrix[bottom][j] cur cur 1 bottom - 1 # 4. 从下到上填充左侧这一列 for i in range(bottom, top - 1, -1): matrix[i][left] cur cur 1 left 1 return matrix用 n4 走一遍会很清楚。第一轮循环把最外圈填满四个边界分别收成 top1、bottom2、left1、right2。第二轮循环再填里圈的 2×2 区域直到 cur 变成 17 退出循环。这里有一个非常容易踩的坑第三步和第四步的 range 写错方向。比如从右到左有人写成 range(left, right 1)结果又往右走回去了自然越界。我自己的习惯是每写一条边的循环先把这个边对应的固定行或列变量写出来再确认起点和终点不要凭感觉写。比如第三步底边固定行号是 bottom列号从 right 到 left那是降序写成 range(right, left - 1, -1)。只要把“固定坐标”和“变化坐标”分开看就不容易错。还有一点值得注意这段代码里第四步执行前可能边界已经交叉但因为计数变量 cur n×n 的限制不会多填。这也是我为什么推荐用 cur 控制循环而不依赖四个边界的相对位置。3.3 对角线之字形遍历从对角线编号到行列坐标推导最后是之字形遍历。这个题的规律性很强每个格子都满足“行坐标 列坐标 对角线编号”。比如在 3×3 矩阵里左上角 (0,0) 的对角线编号是 0元素 5 在 (1,1)编号是 2元素 9 在 (2,2)编号是 4。一共有 mn-1 条对角线编号从 0 到 mn-2。对角线的方向按编号奇偶交替偶数编号从左下到右上奇数编号从左上到右下。按这个规律一行代码都不用写得太复杂def diagonal_order(matrix): if not matrix or not matrix[0]: return [] m, n len(matrix), len(matrix[0]) result [] for s in range(m n - 1): # s 是对角线编号 if s % 2 0: # 偶数编号向上遍历即行坐标从大到小 for i in range(min(s, m - 1), max(0, s - n 1) - 1, -1): result.append(matrix[i][s - i]) else: # 奇数编号向下遍历即行坐标从小到大 for i in range(max(0, s - n 1), min(s, m - 1) 1): result.append(matrix[i][s - i]) return result这里的下界和上界推导是唯一有点绕的地方。对角线上同时满足行坐标 i 在 [0, m-1]、列坐标 s-i 在 [0, n-1] 的范围内。所以行坐标 i 的最小值是 max(0, s-(n-1))最大值是 min(s, m-1)。向上遍历时从最大值降到最小值向下遍历时从最小值升到最大值。用 3×3 矩阵实测一下。s0 是偶数i 从 min(0,2)0 到 max(0,-2)0只取到 (0,0)对应输出 1。s1 是奇数i 从 max(0,-1)0 到 min(1,2)1取 (0,1) 和 (1,0)输出 2、4。s2 是偶数i 从 min(2,2)2 到 max(0,0)0 递减取 (2,0)、(1,1)、(0,2)输出 7、5、3。合起来前八个数是 1 2 4 7 5 3 6 8最后 s4 时取 9结果和预期完全一致。如果不想用对角线分组也可以用前面提到的“二维方向数组 试探修正”来做两种写法我都在实际中用过。分组写法更利于理解数学规律方向数组写法更适合扩展到“随时可能转向”的复杂遍历题。看你自己的偏好。4. 变体题型与举一反三从单一解法到通用能力4.1 变体一逆时针螺旋矩阵知道了顺时针螺旋的写法逆时针只需要调整方向的执行顺序。原来的顺序是右、下、左、上逆时针改成下、右、上、左或者更简单地写成左、下、右、上。核心的边界收缩逻辑完全不变。我建议你把方向顺序抽象成一个数组这样改变顺序只改数组不需要动主体逻辑。这也是代码组织能力的一种体现把变化的部分抽离出来把稳定的部分沉淀下来改起来就不容易出事。4.2 变体二按列蛇形填充按列蛇形就是把“行变量”和“列变量”对调。外层循环遍历列内层循环根据列号的奇偶决定是从上往下还是从下往上填。这个形态在一些图像像素重排场景里会出现本质上和按行蛇形没有任何区别。我在实际中见过有人用同一个函数生成按行蛇形和按列蛇形办法是先正常填充再对矩阵做转置。这是个取巧办法能用但不是最优做法。直接调整行列逻辑更直观而且不浪费额外遍历时间。4.3 变体三给定位置求数值的数学解法有些题目不要求生成整个矩阵而是给出矩阵大小和某个坐标让你直接算出该位置的值。比如在按行蛇形矩阵里位置 (i, j) 的值怎么算这时可以推导每一行起点和数字排列规律用数学公式一步算出时间复杂度从 O(n²) 降到 O(1)。这种题更能考察你对状态规律的理解程度。我不建议一上来就背公式而是先手动推几个小矩阵找出行号奇偶与起始数字、方向之间的关系。推理过程本身就是很好的训练。等你完全理解了推导过程再去记忆或总结公式都不迟。4.4 这类题真正想考察什么面试和笔试里频繁出现蛇形矩阵不是因为实际开发天天要打印这种数字表而是因为它能以很小的代码量考察好几项基本功二维数组下标操作、循环边界控制、状态切换、异常场景处理以及在白板上组织代码的能力。这些能力在真实业务中都会间接用到尤其是处理图像数据、矩阵运算、搜索路径时边界和方向永远是最容易埋 bug 的地方。所以在练这类题目时别只满足于“做出来”。你可以继续问自己几个问题如果 n 等于 0 或 1 会怎样如果矩阵不是正方形而是 m×n 长方形呢如果要从最外层开始每隔一层跳过不填呢每想清楚一个问题你的抽象能力就扎实一分。5. 实操避坑指南那些年我踩过的边界与死循环问题5.1 死循环是怎么来的写蛇形矩阵最常见的故障就是程序跑不完。我总结过几个典型的死循环原因方向指针更新后没有改变任何边界或坐标导致下一步又走回原位置。循环终止条件只用了 left right 这类判断在奇数尺寸矩阵里漏了最后一个中心点循环却卡在中间层反复转圈。在之字形遍历中到达角落时只翻转方向而没有移动坐标下一轮又回到越界走不出去。排查死循环最快的办法是加打印。每填一个数字打印一次当前的坐标、方向和边界值。只要看一眼最后几次输出基本就能定位到是哪条逻辑让“走格子”停不下来。5.2 边界错位和数组越界的经典现场还有一个高发问题是数组越界或者填入位置错误。常见场景是这样螺旋矩阵里填完右侧列后把右边界减一然后执行“从右到左”填充底部一行。这时如果 bottom 和 top 已经交叉就会出现重复覆盖或者读到错误下标。要避免这类问题需要记住一个次序每走完一条边立刻更新对应的边界然后才进入下一条边。千万不要图省事把四个边界留到四条边都填完之后再统一更新那样填第二条边时用的就是过期的边界值。我早期犯过这个错误调试了很久才意识到是“更新时机”不对。另外一个隐蔽问题是从右到左的 range 写错。Python 里 range(left, right 1) 是从小到大而我们需要从大到小必须写成 range(right, left - 1, -1)。之所以很多人写错是因为他们习惯性地把循环起点写成较小的边界。5.3 推荐一套高效的调试节奏针对蛇形矩阵这类题目我推荐一个固定调试套路。第一步先用 n2 或 n3 这样的小矩阵验算法越小越容易手算。第二步在控制台打印每一步的矩阵快照而不是等全部填完才看最终结果否则无法判断哪一步开始出错。第三步给代码加上断言比如每次填入数字时检查当前位置是否为零如果不为零说明发生了重复覆盖直接抛异常这比肉眼观察要可靠得多。我举个例子调试螺旋矩阵时可以这样加断言assert matrix[i][j] 0, f重复覆盖: ({i}, {j}) fila cur # 假设这是你要填入的值 matrix[i][j] fila这个断言能让你第一时间发现边界收缩策略导致的重复覆盖问题。等到代码稳定后再把断言删掉。5.4 常见错误速查表我整理了一个简表方便你刷题时对照检查。症状可能原因解决方法死循环到了边界没有转向检查是否先尝试移动再判断越界数组越界转向后坐标没有修正修正到新起点再翻转方向重复覆盖边界更新顺序不对每填完一条边立即更新对应边界中心点丢失循环条件用了 left right用计数变量 cur n*n 控制循环行列搞反把行号和列号混用先写清楚固定坐标再写变化范围奇偶行方向反了第 0 行和第 1 行定义混淆统一从 0 开始编号别用自然语言行号这个表不一定覆盖所有情况但覆盖了大部分人都会遇到的几类问题。你看到代码不对的时候先对着表自查一遍往往比自己闷头猜更快。6. 真实场景与个人体会蛇形矩阵不是只有刷题才用得到6.1 面试中的高频考察点蛇形矩阵之所以高频出现在面试题里是因为它没有复杂的算法背景纯粹考察编码基本功和逻辑是否严密。面试官能在很短的时间里判断出你是“背了答案”还是“真的理解走格子的过程”。我见过不少候选人能写出正确的最终代码但当面试官追问“如果改成逆时针怎么办”或者“如何用 O(1) 额外空间实现之字形遍历”时就支支吾吾。这说明他们只记住了代码模板没有吃透方向控制的内核。我的建议是刷题时把模板记熟的同时多问自己几个“为什么”和“如果”这样面试官怎么改题你都不慌。6.2 在工程开发中的参考价值抛开面试蛇形矩阵的思想在工程中也有用武之地。图像处理中的像素扫描有时候会采用蛇形或螺旋顺序来提升缓存命中率避免频繁跳跃访问导致性能下降。通信领域里也有类似场景数据交织器会把连续的数据比特按照某种规律重新排列打散突发错误而这种重排思路和矩阵的蛇形/螺旋读写是一致的。就算你日常做业务开发很少直接碰矩阵但“方向 边界”这种抽象模型也能迁移到很多地方。比如游戏里的寻路网格遍历、编辑器的区域填充、报表的蛇形排版等本质上都是在二维结构里按照规则移动。理解了蛇形矩阵再做这些会轻松许多。6.3 我个人沉淀下来的一些心得写了这么多年代码我对这类问题的最大体会是不要试图用“玄学”记忆去写代码而是把规则变成清晰的步骤。走路就是走路方向错了就修正边界到了就转向一切有据可依。蛇形矩阵看似形态各异背后的规律却高度一致理解了共通点遇到任何变体都只是换一个方向序列或者换一种边界收缩策略而已。如果你刚开始练这种题今天可以先动手把按行蛇形填充从零写一遍不参考任何代码。写完再去看螺旋矩阵你会发现思路顺畅很多。这个循序渐进的过程比一次背三份答案有用得多。等三种形态都写顺了再去挑战变体题和数学公式推导整套能力就算真正沉淀下来了。