54. 螺旋矩阵(原题卡片见 经典 150 矩阵、GRD 矩阵)
考点归类
矩阵模拟 · 边界收缩。一圈一圈从外向内走,四条边界(top/right/bottom/left)各自向中心收缩,直到所有元素收集完毕。
思路推导
暴力思路:直接模拟转圈走,每步判断是否越界/重复 → 需要额外 visited 数组 O(m×n) 空间,代码啰嗦。
优化核心:用四个边界值记住”当前环的界限”,指针每走一步先收集当前格,再判断是否撞墙——撞墙则收缩对应的边界并换方向。零额外空间。
两种实现:
- 4 个 for 循环逐条边扫:每次走完整条边,需要
if (top <= bottom)、if (left <= right)守卫防重复(单行/单列矩阵必踩坑) - 指针逐格 + 方向机:每步 push 一次,撞墙收缩后转向
关键洞察:以 res.length < m*n 作为终止条件,可以免掉转向处的边界守卫——因为每次先 push 再转向,收集满后循环条件自然失效,转向动作”死”在循环门口,不会越界访问。
完整代码(推荐:对称收缩)
var spiralOrder = function (matrix) {
if (matrix.length === 0) return []
const rows = matrix.length, cols = matrix[0].length
const res = []
const DIRS = { right: [1,0], down: [0,1], left: [-1,0], up: [0,-1] }
let top = 0, bottom = rows - 1, left = 0, right = cols - 1
let dir = 'right', x = 0, y = 0
while (res.length < rows * cols) {
res.push(matrix[y][x]) // ① 先收集当前格
if (dir === 'right' && x === right) { dir = 'down'; top++ } // 撞右墙→上边完成,收 top
else if (dir === 'down' && y === bottom) { dir = 'left'; right-- } // 撞底墙→右边完成,收 right
else if (dir === 'left' && x === left) { dir = 'up'; bottom-- } // 撞左墙→下边完成,收 bottom
else if (dir === 'up' && y === top) { dir = 'right'; left++ } // 撞顶墙→左边完成,收 left
const [dx, dy] = DIRS[dir]
x += dx; y += dy // ② 沿当前方向走一步
}
return res
}对照自己写的原版(收缩”前方”边界 + up 初始为 1 补偿):也能正确跑通,但 up=1 的来历不直观;对称版改为收缩”身后”边界,四个边界初值天然为 0/0/m-1/n-1。
复杂度
- 时间 O(m×n),每个元素恰好收集一次
- 空间 O(1)(不计输出数组)
易错点
- 方向循环
right→down→left→up→right,转向 = 沿循环走一格。dy 符号别写反:向下 dy=+1,向上 dy=-1(自己原版第三、四个分支就写反过) - 用
else if而非多个if:多个 if 会在转角处级联换向(撞左墙转 up 后可能立刻又满足 y===top),虽然常被终止条件兜住,但逻辑脆弱 - 指针写法以
res.length < m*n终止,天然免掉 4-for 版需要的top <= bottom/left <= right守卫 - 空矩阵要提前返回,否则
matrix[0]越界
变体与举一反三
- 59. 螺旋矩阵 II:反向生成矩阵,把”收集”换成”填入”,骨架完全一致
- 48. 旋转图像:同为矩阵逐层处理,转置+翻转,见 经典 150 矩阵
- GRD 矩阵(GRD 刷题系列)
← 速查卡片见 经典 150 矩阵