54. 螺旋矩阵(原题卡片见 经典 150 矩阵GRD 矩阵

考点归类

矩阵模拟 · 边界收缩。一圈一圈从外向内走,四条边界(top/right/bottom/left)各自向中心收缩,直到所有元素收集完毕。

思路推导

暴力思路:直接模拟转圈走,每步判断是否越界/重复 → 需要额外 visited 数组 O(m×n) 空间,代码啰嗦。

优化核心:用四个边界值记住”当前环的界限”,指针每走一步先收集当前格,再判断是否撞墙——撞墙则收缩对应的边界并换方向。零额外空间。

两种实现:

  1. 4 个 for 循环逐条边扫:每次走完整条边,需要 if (top <= bottom)if (left <= right) 守卫防重复(单行/单列矩阵必踩坑)
  2. 指针逐格 + 方向机:每步 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)(不计输出数组)

易错点

  1. 方向循环 right→down→left→up→right,转向 = 沿循环走一格。dy 符号别写反:向下 dy=+1,向上 dy=-1(自己原版第三、四个分支就写反过)
  2. else if 而非多个 if:多个 if 会在转角处级联换向(撞左墙转 up 后可能立刻又满足 y===top),虽然常被终止条件兜住,但逻辑脆弱
  3. 指针写法以 res.length < m*n 终止,天然免掉 4-for 版需要的 top <= bottom / left <= right 守卫
  4. 空矩阵要提前返回,否则 matrix[0] 越界

变体与举一反三


← 速查卡片见 经典 150 矩阵