54. 螺旋矩阵

LeetCode 原题链接

题目描述

给定一个 m × n 的矩阵 matrix,按照顺时针螺旋顺序返回矩阵中的所有元素。

输入:
[
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
]

输出:[1,2,3,6,9,8,7,4,5]

题型判断

这是一道矩阵边界模拟题。

每完成一圈,就会依次遍历当前矩形的上边、右边、下边、左边,然后将四条边界向内收缩。只要明确每段遍历的起止位置,并在遍历下边和左边前检查边界是否仍然有效,就能保证每个元素只访问一次。

核心思路

维护四条尚未遍历区域的边界:

  • top:上边界。
  • right:右边界。
  • bottom:下边界。
  • left:左边界。

每一圈按下面顺序推进:

  1. 沿上边界从左到右,之后 top++
  2. 沿右边界从上到下,之后 right--
  3. 如果 top <= bottom,沿下边界从右到左,之后 bottom--
  4. 如果 left <= right,沿左边界从下到上,之后 left++

第三、四步的二次边界检查非常重要。当矩阵只剩一行或一列时,前两步可能已经访问完剩余元素;若继续遍历,会产生重复结果。

示例步骤图解

以矩阵为例:

1  2  3  4
5  6  7  8
9 10 11 12

初始边界为 top = 0right = 3bottom = 2left = 0

阶段遍历方向加入答案的元素收缩后的边界
上边左 → 右1, 2, 3, 4top = 1
右边上 → 下8, 12right = 2
下边右 → 左11, 10, 9bottom = 1
左边下 → 上5left = 1
内层上边左 → 右6, 7top = 2

第一圈结束后,尚未访问的区域只剩下:

6 7

所以继续按上边界从左到右访问 6, 7。此后 top = 2,已经大于 bottom = 1,说明没有剩余行,遍历结束。

此时 top > bottom,遍历结束,结果为:

[1,2,3,4,8,12,11,10,9,5,6,7]

变量与区间含义

四条边界共同表示尚未访问的闭合矩形:

行范围:[top, bottom]
列范围:[left, right]
  • 上边遍历固定行 top,列从 leftright
  • 右边遍历固定列 right,行从 topbottom
  • 下边遍历固定行 bottom,列从 rightleft
  • 左边遍历固定列 left,行从 bottomtop

每条边遍历后立刻收缩对应边界,确保四个角不会重复访问。

推进规则

主循环条件为:

top <= bottom && left <= right

它表示仍存在至少一行和一列未访问元素。

遍历完上、右两条边后,剩余区域可能已经为空,因此:

  • 遍历下边前检查 top <= bottom,防止单行矩阵被重复访问。
  • 遍历左边前检查 left <= right,防止单列矩阵被重复访问。

以单行矩阵为例:

1 2 3

遍历上边后已经得到 [1,2,3],并且 top++top > bottom。如果不检查 top <= bottom 就继续遍历下边,会把这一行反向再访问一遍。

以单列矩阵为例:

1
2
3

遍历上边会访问 1,随后遍历右边会访问 2,3,此时 right--left > right。如果不检查 left <= right 就继续遍历左边,会把这一列反向重复访问。

边界条件与易错点

  • 单行矩阵:遍历完上边后就应结束,不能再反向遍历下边。
  • 单列矩阵:遍历完右边后就应结束,不能再向上遍历左边。
  • 非正方形矩阵同样适用,不能假设行列数相等。
  • 四个方向的循环端点都包含边界,使用 <=>=
  • 每遍历完一条边,就应立即收缩对应边界。
  • LeetCode 保证矩阵非空;通用函数可增加空矩阵判断。

代码实现

/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function (matrix) {
  if (matrix.length === 0 || matrix[0].length === 0) {
    return [];
  }

  const result = [];
  let top = 0;
  let right = matrix[0].length - 1;
  let bottom = matrix.length - 1;
  let left = 0;

  while (top <= bottom && left <= right) {
    // 遍历当前上边界:左 → 右。
    for (let column = left; column <= right; column++) {
      result.push(matrix[top][column]);
    }
    top++;

    // 遍历当前右边界:上 → 下。
    for (let row = top; row <= bottom; row++) {
      result.push(matrix[row][right]);
    }
    right--;

    // 可能只剩一行,先确认下边界仍然有效。
    if (top <= bottom) {
      for (let column = right; column >= left; column--) {
        result.push(matrix[bottom][column]);
      }
      bottom--;
    }

    // 可能只剩一列,先确认左边界仍然有效。
    if (left <= right) {
      for (let row = bottom; row >= top; row--) {
        result.push(matrix[row][left]);
      }
      left++;
    }
  }

  return result;
};

复杂度分析

  • 时间复杂度:O(m × n),矩阵中每个元素恰好访问一次。
  • 空间复杂度:O(1),不计返回结果,只使用四个边界变量。