实现思路
方法1: 循环转向 + 标记
1 这个解法的 主要技巧:
- 方向数组:使用二维数组 DIRS 定义四个方向的偏移量,简化方向控制
- 原地标记:通过将访问过的元素设为 Infinity,直接在原矩阵上标记
- 循环转向:使用 取余操作 实现方向的循环切换
- 提前嗅探:每一步都先 "看一看" 下一个位置是否可行,不可行就转向==> "先看再走"
- 边界处理:将 矩阵边界检查 和 已访问检查统一处理==> 都需要进行 转向操作
方法2: 循环转向 + 交替缩短行列
m行, n列的执行序列 规律:
- 向右:n步
- 向下:m-1步
- 向左:n-1步
- 向上:m-2步
- 向右:n-2步
从上可以得知:
- 右左方向, step 和 n 有关
- 下上方向, step 和 m 有关
- 需要有1个变量,记录 [m, n] 的值,然后按循环方向 读取 m/n 值
分析这个序列,发现每次转向后的步数可以表示为:
- 本轮步数 = 上一个垂直方向的步数 - 1
归纳公式:如果用(m,n) 表示当前的维度:
- 水平方向走m步, 垂直方向走n步
- 下一轮的(m,n) 应该是(n-1,m)
本质其实就是 交替减小 m和n 的值
参考文档
代码实现
1 方法1: 循环转向 + 标记 时间复杂度: O(n); 空间复杂度: O(1)
function spiralOrder(matrix: number[][]): number[] {
// 矩阵:m行 * n列
const m = matrix.length, n = matrix[0].length;
// 移动方向顺序:右--> 下--> 左--> 上
const DIRS = [ [0, 1], [1, 0], [0, -1], [-1, 0] ];
// i:当前所在行数; j: 当前所在列数; dirIdx: 当前移动方向索引,和 DIRS 相对应
let i = 0, j = 0, dirIdx = 0;
// 结果列表
let ans = [];
// 从0开始,走的总步数一定是 [0, m*n)
for (let k = 0; k < m * n; k++) {
// 保存当前访问元素,并标记 该元素已访问过
ans.push(matrix[i][j]);
matrix[i][j] = Infinity;
// 嗅探 下一个元素是否到达 左右上下的边界/ 是否已访问过,是的话则 向右转向90度
let nextI = i + DIRS[dirIdx][0], nextJ = j + DIRS[dirIdx][1];
if (
nextI < 0 || nextI >= m ||
nextJ < 0 || nextJ >= n ||
matrix[nextI][nextJ] === Infinity
) {
// 是的话则 向右转向90度
dirIdx = (dirIdx + 1) % 4
}
// 让i和j指向 正确的 新方向的 起始位置
i = i + DIRS[dirIdx][0]
j = j + DIRS[dirIdx][1]
}
// 返回最后结果
return ans
};
方法2: 循环转向 + 交替缩短行列 时间复杂度: O(n); 空间复杂度: O(1)
function spiralOrder(matrix: number[][]): number[] {
let m = matrix.length, n = matrix[0].length;
const size = m * n;
const DIRS = [ [0, 1], [1, 0], [0, -1], [-1, 0] ];
const ans = [];
// 从 (0, -1) 开始
let i = 0, j = -1;
// 根据4个方向进行轮次循环,直到所有数字都被读取了
for (let dirIdx = 0; ans.length < size; dirIdx = (dirIdx + 1) % 4) {
// 每个方向内,每次读取该方向上的一个数字
for (let step = 0; step < n; step++) {
i += DIRS[dirIdx][0];
j += DIRS[dirIdx][1];
ans.push(matrix[i][j]);
}
// 一个方向读取完成后,下一个方向会根据 上一个方向的垂直方向值缩短
// 即:[列1, 行1]--> [行1-1, 列1]--> [列1-1, 行1-1]--> [行1-2, 列1-1]...
// 本质其实就是 交替减小 m和n 的值
[n, m] = [m - 1, n];
}
return ans;
};