本文最后更新于 2026-01-06,文章内容可能已经过时。
「螺旋矩阵」是 LeetCode 中经典的中等难度题目,核心考察矩阵遍历的边界控制与循环逻辑设计。这道题的关键痛点是如何按 右→下→左→上 的螺旋顺序,不重复、不遗漏地遍历矩阵,同时灵活处理不同行数和列数的矩阵(如单行、单列矩阵)。
实际开发中,类似矩阵数据序列化、图形界面元素遍历等场景,都能用到它的核心逻辑。今天我们从最直观的暴力解法出发,一步步优化到逻辑清晰、边界处理完善的最优解,带你吃透螺旋遍历的设计精髓~
📌 题目重述
给你一个 m x n 的整数矩阵 matrix,请你按照顺时针螺旋顺序,返回矩阵中所有元素组成的列表。
螺旋顺序的规则:
从矩阵左上角开始,向右遍历当前行;
到达当前行最右侧后,向下遍历当前列;
到达当前列最下方后,向左遍历当前行(注意:当前行需未被遍历过);
到达当前行最左侧后,向上遍历当前列(注意:当前列需未被遍历过);
重复上述步骤,直到所有元素都被遍历。
举个例子:
1 示例 1 (3×3矩阵):
遍历顺序:
2 示例 2 (4×4矩阵):
遍历顺序:
🚶 阶梯思路拆解
第一步:暴力思路(标记矩阵 + 方向数组)🥾
最直观的想法是,用一个标记矩阵记录哪些元素已经被遍历过,再用一个方向数组控制遍历方向(右→下→左→上),遇到边界或已遍历元素时切换方向,直到所有元素都被遍历。这种方法逻辑简单,容易理解,但需要额外的空间存储标记矩阵。
💡 核心逻辑
初始化标记矩阵 visited( m x n),所有元素初始为 false(未遍历);
定义方向数组 dirs,表示 右、下、左、上 四个方向的行、列偏移量: dirs = [[0,1], [1,0], [0,-1], [-1,0]];
初始化当前位置 (row, col) = (0, 0),当前方向索引 dirIdx = 0(初始方向为右);
循环遍历 m*n 次(矩阵总元素数):
将当前元素 matrix[row][col] 加入结果列表,标记 visited[row][col] = true;
计算下一个位置 (nextRow, nextCol) = (row + dirs[dirIdx][0], col + dirs[dirIdx][1]);
若下一个位置超出矩阵边界,或已被遍历( visited[nextRow][nextCol] = true),切换方向( dirIdx = (dirIdx + 1) % 4);
更新当前位置为新的方向对应的下一个位置;
遍历结束,返回结果列表。
✅ 代码实现(Java)
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> result = new ArrayList<>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return result;
}
int m = matrix.length;
int n = matrix[0].length;
boolean[][] visited = new boolean[m][n];
// 方向数组:右、下、左、上
int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int row = 0, col = 0;
int dirIdx = 0; // 当前方向索引
for (int i = 0; i < m * n; i++) {
// 加入当前元素,标记已遍历
result.add(matrix[row][col]);
visited[row][col] = true;
// 计算下一个位置
int nextRow = row + dirs[dirIdx][0];
int nextCol = col + dirs[dirIdx][1];
// 下一个位置无效(越界或已遍历),切换方向
if (nextRow < 0 || nextRow >= m || nextCol < 0 || nextCol >= n || visited[nextRow][nextCol]) {
dirIdx = (dirIdx + 1) % 4;
}
// 更新当前位置
row += dirs[dirIdx][0];
col += dirs[dirIdx][1];
}
return result;
}
}⚙️ 复杂度分析
🚫 遇到的问题
空间开销过大!当矩阵规模为 10⁴ x 10⁴ 时,标记矩阵会占用大量内存。核心问题是:我们不需要单独记录每个元素是否被遍历,矩阵的边界本身就可以作为遍历的限制条件—— 通过收缩边界,就能实现无重复、无遗漏的遍历。
第二步:优化思路(边界收缩法)🗺️
核心想法是:用四个变量标记矩阵的边界(上、下、左、右),按右→下→左→上的顺序遍历,每遍历完一行或一列就收缩对应的边界,直到边界交叉(所有元素遍历完毕)。这种方法无需额外标记矩阵,空间复杂度优化到 O (1)(除结果列表外)。
💡 核心逻辑
定义四个边界变量:
top:当前未遍历区域的上边界(初始为 0);
bottom:当前未遍历区域的下边界(初始为 m-1);
left:当前未遍历区域的左边界(初始为 0);
right:当前未遍历区域的右边界(初始为 n-1);
循环遍历,直到 top > bottom 或 left > right(所有元素遍历完毕):
右遍历:从左到右遍历当前上边界 top 行,遍历完后 top++(上边界收缩);
下遍历:从上到下遍历当前右边界 right 列,遍历完后 right--(右边界收缩);
左遍历:若 top <= bottom(避免重复遍历单行矩阵),从右到左遍历当前下边界 bottom 行,遍历完后 bottom--(下边界收缩);
上遍历:若 left <= right(避免重复遍历单列矩阵),从下到上遍历当前左边界 left 列,遍历完后 left++(左边界收缩);
遍历结束,返回结果列表。
✅ 代码实现(Java,最优解)
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> result = new ArrayList<>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return result;
}
int m = matrix.length;
int n = matrix[0].length;
int top = 0; // 上边界
int bottom = m - 1; // 下边界
int left = 0; // 左边界
int right = n - 1; // 右边界
while (true) {
// 1. 右遍历:上边界行,从左到右
for (int col = left; col <= right; col++) {
result.add(matrix[top][col]);
}
top++; // 上边界收缩
if (top > bottom) break;
// 2. 下遍历:右边界列,从上到下
for (int row = top; row <= bottom; row++) {
result.add(matrix[row][right]);
}
right--; // 右边界收缩
if (left > right) break;
// 3. 左遍历:下边界行,从右到左(避免单行重复遍历)
if (top <= bottom) {
for (int col = right; col >= left; col--) {
result.add(matrix[bottom][col]);
}
bottom--; // 下边界收缩
}
if (top > bottom) break; // 检查上下是否越界
// 4. 上遍历:左边界列,从下到上(避免单列重复遍历)
if (left <= right) {
for (int row = bottom; row >= top; row--) {
result.add(matrix[row][left]);
}
left++; // 左边界收缩
}
if (left > right) break; // 检查左右是否越界
}
return result;
}
}⚙️ 复杂度分析
✨ 核心优势
时间最优:O (mn) 时间复杂度,与暴力解法一致,但逻辑更清晰;
空间最优:O (1) 额外空间复杂度,无需标记矩阵,大幅节省内存;
边界处理完善:通过 top <= bottom 和 left <= right 的判断,完美处理单行、单列、矩形矩阵等所有场景;
扩展性强:循环逻辑固定,可轻松适配不同规模的矩阵。
第三步:边界情况与易错点
常见边界场景
单行矩阵(如 [[1,2,3,4]]):仅执行右遍历,结果为 [1,2,3,4];
单列矩阵(如 [[1],[2],[3]]):执行右遍历(单行)→下遍历(无)→左遍历(跳过)→上遍历,结果为 [1,2,3];
1x1 矩阵(如 [[5]]):仅执行右遍历,结果为 [5];
矩形矩阵(如 2x5 矩阵):按右→下→左→上循环,边界收缩顺畅,无重复遍历。
易错点提醒
遗漏边界判断:未判断 top <= bottom 或 left <= right,导致单行 / 单列矩阵被重复遍历(如单行矩阵右遍历后又左遍历);
边界收缩顺序错误:遍历完一行 / 列后未及时收缩边界,导致元素重复遍历;
循环终止条件缺失:未在每次边界收缩后判断是否交叉,导致循环无限执行;
矩阵空判断:未处理 matrix 为 null、行数为 0 或列数为 0 的情况,导致空指针异常。
📝 总结
「螺旋矩阵」的解题核心是边界控制与循环逻辑设计,从暴力到最优的递进思路清晰:
暴力解法:标记矩阵 + 方向数组,直观但空间 O (mn);
边界收缩法:利用四个边界变量控制遍历范围,空间 O (1),边界处理完善,是最优解。
关键技巧
边界定义:用 top、bottom、left、right 四个变量框定未遍历区域,遍历一行 / 列就收缩对应边界;
循环顺序:固定右→下→左→上的遍历顺序,确保螺旋方向正确;
边界判断:在左遍历和上遍历前添加条件判断,避免单行 / 单列矩阵重复遍历;
终止条件:每次边界收缩后判断是否交叉,及时终止循环。
同类题扩展建议
掌握了这道题的思路后,可以尝试这些进阶题目,都是同一类边界控制思维的应用:
LeetCode 59. 螺旋矩阵 II:与本题相反,根据给定的 n,生成 n x n 的螺旋矩阵;
LeetCode 885. 螺旋矩阵 III:在更大的网格中按螺旋顺序遍历,需要处理网格外的无效位置;
LeetCode 498. 对角线遍历:按对角线方向遍历矩阵,边界控制思路类似;
LeetCode 1424. 对角线遍历 II:稀疏矩阵的对角线遍历,需要结合哈希表优化,但边界控制逻辑相通。
这类矩阵遍历问题的核心是明确遍历范围→设计遍历顺序→处理边界情况,吃透本题的边界收缩思路,能轻松应对各类矩阵遍历场景~