54. 螺旋矩阵 (Medium)
专题归类: 矩阵 LeetCode 链接: https://leetcode.cn/problems/spiral-matrix/
在线做题: 写 Python 代码并运行 · Java 跳转 LeetCode
题目描述
给你一个 m 行 n 列的矩阵 matrix,请按照顺时针螺旋顺序,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
补充说明:
m == matrix.lengthn == matrix[i].length1 <= m, n <= 10-100 <= matrix[i][j] <= 100- 注意:m 和 n 不一定相等(矩阵不一定是方阵)
题目详细分析
数据范围分析:
- 矩阵最大 10×10 = 100 个元素,非常小,任何合法算法都不会超时。但作为经典题,O(m×n) 是标准解法。
- 元素值范围较小,不重要。
输入输出特征:
- 输入是 m×n 矩阵,输出是一维数组(螺旋顺序)。
- 螺旋顺序:→ 右 (列增加) → ↓ 下 (行增加) → ← 左 (列减少) → ↑ 上 (行减少) → 循环。
- 矩阵不一定是方阵(m 可能不等于 n),边界控制不同。
边界条件:
- 只有一行(m=1)→ 直接按行遍历即可。
- 只有一列(n=1)→ 直接按列遍历即可。
- 单元素矩阵(m=1, n=1)→ 直接返回该元素。
- 矩阵为空(但题目说 m,n >= 1,所以不会出现)。
- 螺旋走到最后可能只剩一行或一列——需要检查边界防止重复遍历。
隐藏条件:
- 螺旋遍历的核心是边界收缩:每走完一条边,对应的边界就向内收缩一层。
- 每次收缩后要检查边界是否交叉,如果交叉说明所有元素已遍历完。
- “右→下”之后,在走”左→上”之前必须检查边界是否仍然有效,否则可能重复遍历。
小白版直白理解
想象你在一栋楼的迷宫里,迷宫是矩形,你站在左上角入口。你的任务是顺时针转圈把所有房间都走一遍,并记录房间号。
就像一条贪吃蛇:先往右走到底(但不能出墙),然后往下走到底,然后往左走到底,然后往上走到底……但每次走完一条边,墙就会向内缩进一格。
简单步骤:
- 往右走,走完就把”上墙”往下推一格
- 往下走,走完就把”右墙”往左推一格
- 往左走,走完就把”下墙”往上推一格
- 往上走,走完就把”左墙”往右推一格
- 重复 1~4,直到四面墙挤在一起
生活中的类似场景:
- 剥洋葱:一层一层从外往里剥
- 削苹果:螺旋削皮
- 打印机的喷墨路径
解题思路
思路一:边界收缩法(推荐)
核心洞察:
维护四个边界:top, bottom, left, right。按顺时针方向遍历四条边,每遍历一条边就将对应的边界收缩一格。
关键点在于:在遍历下边和左边之前,需要检查边界是否已经交叉,以防只剩一行或一列时重复遍历。
def spiralOrder(matrix):
res = []
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
while top <= bottom and left <= right:
# 1. 从左到右遍历上边
for j in range(left, right + 1):
res.append(matrix[top][j])
top += 1
# 2. 从上到下遍历右边
for i in range(top, bottom + 1):
res.append(matrix[i][right])
right -= 1
# 3. 从右到左遍历下边(需检查 top <= bottom)
if top <= bottom:
for j in range(right, left - 1, -1):
res.append(matrix[bottom][j])
bottom -= 1
# 4. 从下到上遍历左边(需检查 left <= right)
if left <= right:
for i in range(bottom, top - 1, -1):
res.append(matrix[i][left])
left += 1
return res复杂度: O(m × n) 时间,O(1) 额外空间(输出数组不计)。
思路二:方向数组 + 访问标记
定义方向数组 [(0,1), (1,0), (0,-1), (-1,0)] 分别对应右、下、左、上。当遇到边界或已访问的元素时转向。
def spiralOrder_direction(matrix):
m, n = len(matrix), len(matrix[0])
# 方向:右、下、左、上
dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)]
visited = [[False] * n for _ in range(m)]
res = []
row = col = dir_idx = 0
for _ in range(m * n):
res.append(matrix[row][col])
visited[row][col] = True
# 计算下一步
nr, nc = row + dirs[dir_idx][0], col + dirs[dir_idx][1]
# 如果下一步越界或已访问,转向
if nr < 0 or nr >= m or nc < 0 or nc >= n or visited[nr][nc]:
dir_idx = (dir_idx + 1) % 4
nr, nc = row + dirs[dir_idx][0], col + dirs[dir_idx][1]
row, col = nr, nc
return res复杂度: O(m × n) 时间,O(m × n) 空间(visited 数组)。
思路三:逐层剥离(递归思路)
递归处理矩阵的每一层(从外到内),每次处理一圈后递归处理内层子矩阵。
def spiralOrder_recursive(matrix):
res = []
def add_layer(t, b, l, r):
if t > b or l > r:
return
# 上边:左 → 右
for j in range(l, r + 1):
res.append(matrix[t][j])
# 右边:上 → 下
for i in range(t + 1, b + 1):
res.append(matrix[i][r])
# 下边和左边需要检查
if t < b:
for j in range(r - 1, l - 1, -1):
res.append(matrix[b][j])
if l < r:
for i in range(b - 1, t, -1):
res.append(matrix[i][l])
# 递归处理内层
add_layer(t + 1, b - 1, l + 1, r - 1)
add_layer(0, len(matrix) - 1, 0, len(matrix[0]) - 1)
return res易错点
- 下边和左边的边界检查:遍历完上边和右边后,
top和right已更新。在遍历下边前必须检查top <= bottom(防止只剩一列时重复遍历),在遍历左边前必须检查left <= right(防止只剩一行时重复遍历)。 - range 的步长:从右到左遍历时
range(right, left-1, -1),从下到上遍历时range(bottom, top-1, -1)。注意left-1和top-1是因为range是左闭右开区间。 - 非方阵处理:当 m ≠ n 时,螺旋遍历到最后可能只剩一行或一列,边界检查变得尤为重要。
- 单行/单列:如果只有一行(top == bottom),遍历上边就够了,不需要再遍历下边。
- 死循环:没有正确更新边界或边界检查条件不对可能导致死循环。
框架提炼
螺旋遍历通用模板(边界收缩法):
def spiralOrder(matrix):
if not matrix or not matrix[0]:
return []
# Step 1: 初始化四边界
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
result = []
# Step 2: 循环遍历
while top <= bottom and left <= right:
# 上边 →
for j in range(left, right + 1):
result.append(matrix[top][j])
top += 1
# 右边 ↓
for i in range(top, bottom + 1):
result.append(matrix[i][right])
right -= 1
# 下边 ← (需 top <= bottom)
if top <= bottom:
for j in range(right, left - 1, -1):
result.append(matrix[bottom][j])
bottom -= 1
# 左边 ↑ (需 left <= right)
if left <= right:
for i in range(bottom, top - 1, -1):
result.append(matrix[i][left])
left += 1
return result变体应用:
- 螺旋矩阵 II(生成螺旋矩阵)→ 59-螺旋矩阵 II
- 螺旋遍历的逆过程
- 从任意起点、任意方向开始螺旋遍历
关联题目
- 59-螺旋矩阵 II — 本题的逆向问题:给定 n,生成一个 n×n 的螺旋矩阵。同样用边界收缩法,但方向是赋值而不是读取。
- 48-旋转图像 — 矩阵旋转问题,同样是矩阵操作,但用的是转置+翻转的组合思路。
- 73-矩阵置零 — 也是矩阵原地操作问题,核心是标记与修改分离。
- 885-螺旋矩阵 III — 变体:从矩阵中心开始螺旋遍历,边界控制更复杂。