54. 螺旋矩阵
!abstract 一句话本质 就是模拟"顺时针走螺旋"的过程,不是真的有数学公式,而是"沿当前方向走,撞墙(越界或已访问)就右转 90°",走满 m·n 步停。
!quote 链接
题目
给一个 m×n 矩阵,按顺时针螺旋顺序返回所有元素(从左上角出发,右→下→左→上 循环)。
思路(从暴力到最优)
最直接的想法:模拟人走螺旋的过程。
- 维护一个"当前方向",用方向向量
[[0,1],[1,0],[0,-1],[-1,0]]分别表示 右、下、左、上。 - 走到下一格之前,先看"按当前方向走会不会撞墙":
- 越界(出矩阵)
- 已访问过
- 撞墙就
di = (di + 1) % 4右转,再走。 - 走满 m·n 步就停。
怎么标记"已访问"?两个办法:
- 原地修改 :把访问过的格子设成一个哨兵值(如
Infinity),空间 O(1) 但破坏原矩阵。← 本解法 - 额外 visited 矩阵:空间 O(m·n),不破坏原矩阵。
另一种写法 ------ 收缩边界法 : 维护 top/bottom/left/right 四个指针,每走完一行/列就收缩对应边界。优点是不破坏原矩阵、空间 O(1),而且对"生成螺旋矩阵"(螺旋矩阵 II)更自然。缺点是四个方向要分别写循环,代码长一点。
收缩边界法详解
四个边界含义:
top:当前还没处理过的最上行bottom:当前还没处理过的最下行left:当前还没处理过的最左列right:当前还没处理过的最右列
遍历顺序固定为:
- 从左到右遍历
top行,然后top++ - 从上到下遍历
right列,然后right-- - 检查
top <= bottom,再从右到左遍历bottom行,然后bottom-- - 检查
left <= right,再从下到上遍历left列,然后left++
每轮循环后,有效矩阵向内收缩一圈。步骤 3 和 4 必须先检查边界再遍历,因为单行或单列矩阵走完前两步之后,已经不存在可走的另一边了。
javascript
var spiralOrder2 = function(matrix) {
const m = matrix.length, n = matrix[0].length;
let top = 0, bottom = m - 1, left = 0, right = n - 1;
const ans = [];
while (top <= bottom && left <= right) {
// 向右:top 行,left → right
for (let j = left; j <= right; j++) ans.push(matrix[top][j]);
top++;
// 向下:right 列,top → bottom
for (let i = top; i <= bottom; i++) ans.push(matrix[i][right]);
right--;
// 向左:bottom 行,right → left
if (top <= bottom) {
for (let j = right; j >= left; j--) ans.push(matrix[bottom][j]);
bottom--;
}
// 向上:left 列,bottom → top
if (left <= right) {
for (let i = bottom; i >= top; i--) ans.push(matrix[i][left]);
left++;
}
}
return ans;
};
时间复杂度同样是 O(m·n),额外空间 O(1),并且不修改原矩阵。
关键卡点
!warning 我当时卡在这
- 方向向量顺序:必须是 右→下→左→上(顺时针),顺序错了输出全乱。
- 越界检查必须放在"已访问检查"前面 :如果
x, y已越界,再读matrix[x][y]在某些语言会直接报错(JS 里是undefined,侥幸不崩但是坏习惯)。- 1×n 矩阵的隐患 :循环靠
k < m*n卡次数终止,最后一格转弯后的越界坐标实际没被用到。能过题,但要意识到是"次数卡得准"救了你。- 本题是看题解后写出来的,对"收缩边界法"还不熟,下次复习时要手写一遍收缩边界法。
代码
javascript
var spiralOrder = function(matrix) {
// 方向向量:右、下、左、上(顺时针)
const DIR = [[0, 1], [1, 0], [0, -1], [-1, 0]];
const m = matrix.length, n = matrix[0].length;
const ans = [];
let i = 0, j = 0, di = 0;
for (let k = 0; k < m * n; k++) {
ans.push(matrix[i][j]);
matrix[i][j] = Infinity; // 标记已访问(原地修改)
// 预判下一格
const x = i + DIR[di][0];
const y = j + DIR[di][1];
// 越界 || 已访问 → 右转
if (x < 0 || x >= m || y < 0 || y >= n || matrix[x][y] === Infinity) {
di = (di + 1) % 4;
}
i += DIR[di][0];
j += DIR[di][1];
}
return ans;
};
复杂度
- 时间:O(m·n),每个格子访问一次。
- 空间:O(1)(不算答案数组;原地修改的代价是破坏输入)。
一句话记忆点
!tip 下次看到类似题 螺旋遍历 → 方向向量
右下左上+ 撞墙右转(di+1)%4;不能改原矩阵就上收缩边界法(top/bottom/left/right 四指针)。
相似题 / 套用模板
- 59. 螺旋矩阵 II(生成螺旋矩阵)→ 收缩边界法更好写
- 73. 矩阵置零(同专题,矩阵原地操作)
- 方向向量 + 撞墙转向 模板可迁移到所有"按固定方向走、遇阻转向"的模拟题
复习记录
2026-08-11 第 1 次复习
结果:未通过 ❌(阶段 1→0,重置为 0,3 天后复习 2026-08-14)
记得牢:
- 代码逻辑正确,用收缩边界法 AC(四边界
l/r/t/b+ 每步收缩 +break判越界),用[[1,2,3],[4,5,6],[7,8,9]]验证输出[1,2,3,6,9,8,7,4,5]正确。 - 客观上完成了"手写收缩边界法"的要求(虽然自己没意识到)。
需要加强:
- 方法认知混淆 :写了收缩边界法却以为是"模拟",分不清两种写法------①方向向量模拟法(指针+方向变量
di+visited 标记/原地改);②收缩边界法(四边界+收缩,无需 visited、无需方向变量)。必须能识别自己写的是哪种。 - 不独立:自述看题解写出,复习要求凭记忆独立完成。
- 复杂度记法 :把 O(m·n) 写成 O(N²)------量级直觉有部分道理(两维相乘),但记法不规范;O(N²) 特指单变量平方,只有方阵(m=n=N)才成立。空间方面质疑了"输出数组算不算额外空间"------约定是输出数组不计入额外空间,所以额外空间 O(1);省下的是 visited 矩阵那 O(m·n)。
- 下次复习重点:独立默写收缩边界法 + 准确说出 O(m·n) 时间 / O(1) 额外空间 + 能区分两种模拟写法。