作为刷完hot100接近三遍的人,螺旋矩阵这道题在我心里的地位一直很特殊。它不像动态规划那样需要复杂的推导,也不像图论那样需要精巧的算法设计,它就是一道纯粹的“模拟题”,但每次写,都有人会在边界条件上栽跟头。甚至可以说,这道题是面试中检验候选人“代码基本功是否扎实”的一道非常典型的分水岭。你问一个刷题量过百的人会不会做,他多半会说会,但你让他十五分钟内在白板上写出来且一次通过,很多人会卡壳。
这篇文章,我就把这题从题意理解到代码实现,再到面试考场上可能遇到的变体和追问,整个过程掰开揉碎讲清楚。尤其是那些“为什么这么写”和“哪里容易错”的细节,我会重点讲。
1. 这题到底在考什么:理解“模拟遍历”与“边界收缩”的本质
螺旋矩阵,全称是“螺旋矩阵 Spiral Matrix”,题目本身并不复杂:给你一个 m 行 n 列的矩阵 matrix,要求按照顺时针螺旋顺序,返回矩阵中的所有元素。
举个最典型的例子,输入三维矩阵:
text复制[[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]]
期望的输出是 [1, 2, 3, 6, 9, 8, 7, 4, 5]。
从箭头的走向来看,就是从外圈到内圈,像剥洋葱一样,一层一层往里走。这个走向从视觉上很好理解,但一旦落到代码上,你会发现难点不在于“理解题意”,而在于用代码“描述这次行走”。
1.1 为什么这道题被归类为“模拟”
LeetCode hot100 的题目,基本分为几大类:动态规划、深度优先搜索、广度优先搜索、双指针、滑动窗口、哈希表、贪心等,而螺旋矩阵在类型上属于“数组”和“模拟”。
所谓“模拟”,就是题目本身并没有隐藏在背后的数学公式,也不需要复杂的算法设计,你需要做的就是按题目描述的步骤,把每一步的行走路径忠实地用代码翻译出来。因此,这道题对代码的“精细度”要求特别高,尤其是控制循环边界和变量更新时,稍有疏忽就会导致索引越界或者漏元素。
类似“模拟”风格的题还有“斐波那契数列”(虽然它能用矩阵快速幂优化),以及 hot100 里的“旋转图像”。这些题的特点都是一眼能看懂,但下手写代码时会发现细节极多。
1.2 解决螺旋矩阵的两条主流思路
我见过、也用过两类主要解法:
第一类:按层模拟(边界收缩法)
这是最正统、最容易让人理解的方法。核心思想是用四个变量记录当前未遍历区域的上下左右边界:top、bottom、left、right。然后在一个循环里:
- 先从左到右遍历上边界那一行,遍历完
top++; - 再从上到下遍历右边界那一列,遍历完
right--; - 然后从右到左遍历下边界那一行,遍历完
bottom--; - 最后从下到上遍历左边界那一列,遍历完
left++。
每完成一轮,四个边界就往内收缩一圈,直到边界交错,说明全部元素都已遍历。
第二类:方向数组 + 访问标记法
用两个方向数组模拟“行走”的向量,比如 dirs = [(0,1), (1,0), (0,-1), (-1,0)] 分别代表右、下、左、上。每次前进时,先判断下一步是否越界,或者是否走进已访问过的格子,如果是,就切换方向。同时维护一个 visited 二维数组做标记。
这类方法代码结构上更像“机器人走路”,理解起来直觉性也很强,但需要额外的 O(m*n) 空间来记录访问状态。与之相比,第一类边界收缩法只需要常数级别额外空间,空间复杂度上是更优的。
我在面试中推荐优先掌握第一类方法,不是说第二类不对,而是第一类让你对矩阵边界的理解更深入,且空间复杂度更漂亮。当然,两种方法都值得练一练,在写变体题时会交替用到。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 边界收缩法的代码骨架:从“思路”到“实现”的精细工程
理解了核心思路是“剥洋葱”之后,我们直接看代码实现。这里我给出最简洁也最容易理解的 Python 写法,然后逐段拆解。
python复制from typing import List
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix or not matrix[0]:
return []
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
res = []
while top <= bottom and left <= right:
# 1. 从左到右遍历上边界
for col in range(left, right + 1):
res.append(matrix[top][col])
top += 1
# 2. 从上到下遍历右边界
for row in range(top, bottom + 1):
res.append(matrix[row][right])
right -= 1
# 3. 从右到左遍历下边界(需检查上下边界是否仍有效)
if top <= bottom:
for col in range(right, left - 1, -1):
