资讯动态

螺旋矩阵遍历详解:从边界控制到代码实现

发布时间:2026/10/21 16:02:04 来源:尧图企业网站定制
很多人第一次看到“螺旋矩阵遍历”这个题目第一反应是这不就是一层一层绕圈吗好像很简单。但真上手去写十个里有八个会在边界条件上报错要么数组越界要么重复访问要么中心点漏掉。这个题目看起来小巧实际上非常考验对下标控制、循环不变量和状态切换的理解。我最早是在算法训练里被这道题折磨过后来在实际项目中做图像分块和表格数据重组又遇到类似的逻辑才彻底把它吃透。这篇文章想做的就是把螺旋矩阵遍历从思路到实现完整拆开讲清楚“从边界到中心”这层控制到底怎么落地。不管你是刚接触算法的初学者还是需要用矩阵遍历做图像处理、数据可视化的开发者都应该能从这里拿到能直接用的代码和避坑经验。1. 螺旋矩阵遍历到底在解决什么问题1.1 一次“回字形走格子”的抽象螺旋矩阵遍历说白了就是给你一个二维数组按照顺时针方向从最外层开始一圈一圈往里面走最后把数组里每个元素恰好访问一次。如果用一个词概括就是“回字形遍历”。想象一下你面前铺了一张网格纸从左上角开始先往右走走到不能再走然后往下走走到不能再走再往左走回到这一行的左端最后往上走回到起点的下一格。这样你就绕完了一圈。接着把外面这一圈当作“已经用掉的墙”进入里面一层继续做同样的事。这个方法听起来很机械但恰恰因为它机械才适合被写成程序。程序最擅长的就是重复执行固定动作只要你能把“一圈”这个动作定义清楚剩下的只是循环多少次的问题。1.2 常见应用场景从图像到数据布局可能有人觉得这只是一个算法题实际工作中用不到。我一开始也这么以为直到后来做了几个和图像处理、表格数据相关的模块才明白螺旋遍历是很多底层逻辑的骨架。在图像处理中有一种常见的操作是把图片分块后进行“由外向内”的像素读取比如做边缘检测时优先处理最外圈的像素再逐步向内收缩这样可以减少无效区域的扫描。在数据可视化领域一些热力图的渲染也会把数据矩阵按螺旋顺序重新排列让视觉重心从中心向外扩散或者反过来从外圈向内聚合。另一个典型场景是行列转置、矩阵旋转相关的算法优化。很多操作本质上不是直接改值而是改变遍历顺序。理解了螺旋遍历后面再看“顺时针旋转90度”“逆时针旋转180度”这类题目会轻松很多。可以说这不是一个孤立的技巧而是一类“按规则访问网格数据”的基础能力。1.3 算法模型的输入输出拆解从计算模型的角度看螺旋遍历算法的输入是一个二维矩阵输出一般有两种形式一种是按螺旋顺序生成的一维数组也叫“螺旋序”另一种是原地修改矩阵重新填值比如“螺旋填充数字”。我早期踩过的一个坑就是把这两种玩法混在一起。判断一个算法要不要用额外数组可以先问自己目标是“读取”还是“写入”读取只要一个列表存结果写入则往往需要先创建一个同等大小的矩阵。目标不同代码里的边界处理和初始化方式都会不一样。2. 主流解法与核心控制思想2.1 分层剥洋葱一圈一圈处理讲螺旋遍历最直观的思路就是“分层剥洋葱”。把矩阵看成一层一层的洋葱皮最外面是一层往里剥一层再往里一层直到剥完。每一层可以用四个边界来定义top当前层的上边界行索引bottom当前层的下边界行索引left当前层的左边界列索引right当前层的右边界列索引初始时这四个边界就是整个矩阵的边界。每遍历完一圈top加一、bottom减一、left加一、right减一表示这层已经被“吃掉”下一循环处理内部区域。这种思路好理解写起来也不容易出错关键在于每一圈内的四条边怎么走。我的习惯是固定一条规则从当前左上角开始向左走到右上角从上到下走到右下角从右到左走到左下角从下到上走回左上角的下方。这个规则保持稳定每一层都按同样的套路执行代码自然清晰。2.2 方向转向模拟让每一步都有状态另一种常见思路是“方向模拟”。给当前位置加一个方向状态比如右、下、左、上然后每一步都尝试往前走。如果发现下一个位置已经超出边界或者已经访问过就顺时针转向。这种方法更接近人走路的感觉一路走到头遇到墙就右转。和分层法相比它少了“一圈一圈”的结构感但更通用。有些变体题目比如“从矩阵中心开始逆时针输出”“按照蛇形路径遍历”用方向模拟会更容易扩展。方向模拟的核心是方向数组。比如directions [(0, 1), (1, 0), (0, -1), (-1, 0)]分别代表右、下、左、上。每次转向就是让方向索引加一再对4取模保证循环。是否要转向则需要看下一个位置是否合法。2.3 为什么说“边界控制”是灵魂不管是分层法还是方向模拟你都会发现表面上的难点是“方向切换”真正的难点其实是“边界控制”。四个边界的值什么时候更新、更新到什么程度、访问时要不要越界检查只要有一个地方差了一个1整个结果就乱了。我见过很多人写螺旋遍历思路完全正确但是代码里到处都是1、-1调了半天还是不对。原因在于他们没有把边界的语义定义清楚。我个人的习惯是把某个边界值理解为“当前有效区域的第一个索引”或者“最后一个有效索引”。一旦定义清楚代码里所有判断都要围绕这个定义来。比如我定义right代表当前层最后一列的有效索引那么向右遍历时循环条件就是col right遍历完之后right - 1。如果一会儿把这个边界当开区间一会儿当闭区间必然出错。3. 从零手写螺旋遍历代码与边界推导3.1 分层法的Python实现先上一版我最常用的分层法实现。假设输入的矩阵是matrix行数是m列数是n返回螺旋顺序的一维列表。def spiral_order(matrix): if not matrix or not matrix[0]: return [] m, n len(matrix), len(matrix[0]) top, bottom 0, m - 1 left, right 0, n - 1 result [] while top bottom and left right: # 向右顶部一行从左到右 for col in range(left, right 1): result.append(matrix[top][col]) # 向下右侧一列从上到下 for row in range(top 1, bottom 1): result.append(matrix[row][right]) # 向左底部一行从右到左 if top bottom: for col in range(right - 1, left - 1, -1): result.append(matrix[bottom][col]) # 向上左侧一列从下到上 if left right: for row in range(bottom - 1, top, -1): result.append(matrix[row][left]) top 1 bottom - 1 left 1 right - 1 return result这段代码看起来不长但每一行都有讲究。第二段向下遍历时起始行是top 1因为右上角那个元素已经在向右遍历时被访问过了向左遍历时起始列是right - 1因为右下角元素已经在向下遍历时被访问过了向上遍历时起始行是bottom - 1因为左下角元素已经在向左遍历时被访问过了。3.2 方向模拟法的Python实现再看方向模拟法。这种写法需要借助一个visited矩阵记录每个位置是否被访问过所以额外空间会多一点。def spiral_order_direction(matrix): if not matrix or not matrix[0]: return [] m, n len(matrix), len(matrix[0]) visited [[False] * n for _ in range(m)] directions [(0, 1), (1, 0), (0, -1), (-1, 0)] direction_index 0 row, col 0, 0 result [] for _ in range(m * n): result.append(matrix[row][col]) visited[row][col] True next_row row directions[direction_index][0] next_col col directions[direction_index][1] if (next_row 0 or next_row m or next_col 0 or next_col n or visited[next_row][next_col]): direction_index (direction_index 1) % 4 next_row row directions[direction_index][0] next_col col directions[direction_index][1] row, col next_row, next_col return result方向模拟法最需要注意的是转向之后下一步的坐标要重新计算而不是在原坐标上继续。我在初学阶段犯过一个经典错误转向时只改了方向索引没有重新计算next_row和next_col导致下一步还是往原来的方向走结果死循环。3.3 把下标算明白防止数组越界的关键写代码的时候下标是最容易翻车的地方。很多初学者问为什么向右遍历用了range(left, right 1)向下遍历却用range(top 1, bottom 1)这得回到边界语义来看。这里我把top、bottom、left、right都定义为“有效位置的下标”。向右遍历时从left到right的每个列都是当前层的所以必须包含right写成right 1。但是向下遍历时右上角matrix[top][right]已经被取过了所以行要从top 1开始又因为右下角matrix[bottom][right]还需要被取到所以终点是bottom 1。向左遍历同理。从right - 1开始一直走到left这一步终点必须包含left所以用range(right - 1, left - 1, -1)。向上遍历时从bottom - 1开始一直走到top 1因为top行左侧的matrix[top][left]已经在向右遍历时取过了。我建议各位在纸上画一个3×3的矩阵手动模拟每一行代码的取值过程。画完一遍很多“为什么这里要加一”的疑问会自然消失。3.4 矩阵规模的特殊情况处理如果说常规方阵还能靠直觉硬写空矩阵、单行、单列、单元素就需要特别小心了。空矩阵matrix是[]或者matrix[0]是[]。代码开头如果没有not matrix or not matrix[0]的判断后面访问len(matrix[0])就会直接报错。单行矩阵比如[[1, 2, 3]]。分层法在向下遍历时top bottom不成立所以底部向左那一段不会执行向上遍历也同理不会执行。这样输出结果就是[1, 2, 3]正确。单列矩阵比如[[1], [2], [3]]。向右遍历后向下遍历能取到[2, 3]但因为left right不成立向上那段不会执行。结果正确。单元素矩阵[[1]]更简单。向右遍历取到1然后top bottom、left right后面的条件分支都不会进入最后输出[1]。这些特殊情况在题目中经常作为边界测试用例出现写代码时务必要保证它们都能通过。我把这些小分支称为“安全阀”没有它们逻辑再对也会崩。4. 现场实录调试螺旋遍历时的常见问题与排查4.1 奇数尺寸矩阵的中心点丢了怎么办我第一次写分层法时用的矩阵是5×5结果输出少了最中间那个数最后一圈只绕了三边就结束了。排查半天发现是循环条件写成了while top bottom and left right而不是。问题出在中心点。当矩阵边长是奇数时剥到最后会剩下一个单独的元素此时top bottom且left right。如果你用作为循环继续的条件这个元素会被挡在循环外面永远不会被访问。解决方案有两种要么把循环条件改成while top bottom and left right让最后一圈也能进入要么单独在循环结束后判断一下如果还有中心元素就补进去。我推荐第一种因为更通用而且代码更简洁。4.2 非方阵矩阵遍历顺序错位方阵写熟了遇到长条矩形比如3行5列很容易在“向左遍历”那一段出错。原因还是边界收缩后内部区域可能不再是完整的方形甚至会出现某一行或某一列已经被“吃掉”的情况。分层法里我专门加了两个判断if top bottom才执行底部向左if left right才执行左侧向上。这两个条件不是可有可无而是为了防止在最后一行或最后一列上重复遍历。举一个3行4列的例子。当外部循环执行完第一圈后剩下的内部区域是一行两列此时top bottom但left right。如果不加top bottom判断底部向左遍历会把已经访问过的元素再访问一遍结果出现重复值如果加了判断就只会向右遍历一次结果正确。4.3 方向模拟法陷入死循环方向模拟法最常见的报错是IndexError或者程序不报错但一直跑不完。前者通常是没做边界检查后者则是转向逻辑有漏洞。我排查这类问题通常分三步走第一步检查边界判断。next_row和next_col是否同时满足0 值 对应维度只要有一个不满足就必须转向。第二步检查转向后是否重新计算了下一步。部分人在这里犯懒以为改个方向就行实际上转向之后当前位置没变必须重新算下一个位置否则会原地打转。第三步检查访问标记。方向模拟法每一步都要把当前位置标记为True否则它会不断走回已经访问过的格子然后陷入“转向-前进-回到旧格-再转向”的死循环。我把这三步写成了一张排查清单之后每次写类似的方向模拟题都按这个顺序检查基本能一次性通过。5. 复杂度、变体与扩展思路5.1 时间与空间复杂度到底是多少从时间复杂度的角度看无论分层法还是方向模拟法每个元素都只被访问一次。对于m × n的矩阵时间复杂度都是O(m × n)不存在更优的可能因为你至少要看到每个元素才能输出它。空间复杂度要分情况讨论。如果只返回结果列表那么结果列表本身算输出不计入额外空间分层法除了几个整型变量外没有额外数组所以额外空间是O(1)。方向模拟法需要用visited数组记录访问状态额外空间是O(m × n)这一点在内存敏感的场景下需要留意。如果题目要求“原地螺旋填充数字”也就是在一个新矩阵里按螺旋顺序填值那本质上也是O(m × n)的额外空间因为你至少需要一个同样大小的矩阵来保存结果。有些题解声称原地实际上只是把结果矩阵当作了输出不算额外空间。5.2 变体玩法逆时针、螺旋填数、旋转矩阵螺旋遍历算法有很多近亲。最常见的变体是逆时针螺旋做法很简单把方向数组的顺序换一下就行。顺时针是“右、下、左、上”逆时针就是“下、右、上、左”或者按你的起始位置调整。另一种常见变体是“螺旋填数”。给定一个n×n的空矩阵从1开始按螺旋顺序填到n²。这其实是遍历算法的反操作遍历是读填数是写。你可以复用方向模拟法的壳子把result.append换成matrix[row][col] value然后每次value 1。还有一类题目是旋转矩阵。虽然旋转矩阵和螺旋遍历不完全是一回事但如果你把螺旋遍历的下标变化规律吃透了旋转矩阵的坐标映射会变得很好理解。比如顺时针旋转90度本质上是把每个元素从(i, j)映射到(j, n - 1 - i)这和螺旋方向变化有很强的关联。5.3 工程落地与面试描述的建议在实际工程里我不会轻易用方向模拟法除非矩阵规模不大且visited数组的内存开销可以接受。分层法因为额外空间是O(1)更适合嵌入到图像处理、大数据分块这类场景里。面试场景中我更推荐优先讲清分层法。它的逻辑更直观边界收缩的每一步都能用文字描述清楚。面试官追问边界条件时你可以主动说“空矩阵、单行、单列、奇数中心点都需要单独考虑”这会是加分项。如果你准备用方向模拟法回答一定要把“方向数组”和“转向后重新计算坐标”这两个点讲清楚因为这正是容易写错的地方也是面试官最爱追问的细节。我见过不少候选人思路对但代码里忘了重置坐标最后圆不回来。与其硬背不如亲手多写几遍形成肌肉记忆。结尾螺旋矩阵遍历看似是一个小题目但它在“用循环模拟方向变化”这个层面上非常值得反复练习。我个人在实际操作中的体会是这类问题最忌讳的就是凭感觉改下标哪怕只是差一个数字整个遍历顺序就会崩掉。每次写完我都会用几组特殊用例跑一遍空矩阵、单行、单列、奇数方阵、非方阵确保所有边界都稳了才放心。最后再分享一个小技巧如果你在面试或者考试中时间紧张优先写分层法。它不需要额外数组代码结构清晰只要在循环条件上记得用基本不会出大问题。方向模拟法虽然更灵活但需要维护状态写起来容易出错。两种方法都练熟之后你会发现螺旋遍历类题目的核心其实就是一个词——控制边界。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑