LeetCode 54. 螺旋矩阵(顺时针遍历)
题目描述
给你一个 m 行 n 列的矩阵 matrix,请按照 顺时针螺旋顺序,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
算法思路
我们可以把螺旋遍历看作一个人在矩阵中沿顺时针方向行走,遇到边界或已经走过的格子就右转。这样只要模拟这个行走过程,就能按顺序访问所有元素。
关键点
- 方向控制 :使用方向数组
dx[]和dy[]表示四个方向(右、下、左、上),顺序严格按顺时针排列。 - 访问标记 :用布尔数组
st记录每个位置是否已被访问,避免重复。 - 转向条件:每次尝试向前走一步,如果下一步越界或已访问,则立刻顺时针旋转 90°,再重新计算下一步。
模拟过程
- 从左上角
(0, 0)出发,初始方向为 向右。 - 每走到一个格子,就将其值加入结果数组,并标记为已访问。
- 根据当前方向计算下一个格子的坐标。
- 如果下一个格子不合法(越界或已访问),则顺时针改变方向,重新计算下一个格子。
- 重复步骤 2~4,直到走完所有格子(共
m * n个)。
代码实现(C++)
cpp
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res; // 存储结果
int n = matrix.size(); // 行数
if (!n) return res; // 空矩阵直接返回
int m = matrix[0].size(); // 列数
// 方向数组:右、下、左、上(顺时针)
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
// 访问标记数组
vector<vector<bool>> st(n, vector<bool>(m, false));
// i 记录已访问元素个数,x,y 为当前位置,d 为当前方向索引
for (int i = 0, x = 0, y = 0, d = 0; i < n * m; i++) {
res.push_back(matrix[x][y]); // 记录当前元素
st[x][y] = true; // 标记已访问
// 尝试按当前方向走一步
int a = x + dx[d], b = y + dy[d];
// 如果下一步越界或已访问,则右转
if (a < 0 || a >= n || b < 0 || b >= m || st[a][b]) {
d = (d + 1) % 4; // 顺时针旋转方向
a = x + dx[d];
b = y + dy[d];
}
// 移动到下一个位置
x = a;
y = b;
}
return res;
}
};
复杂度分析
- 时间复杂度 :
O(m * n),每个元素恰好被访问一次。 - 空间复杂度 :
O(m * n),额外使用了一个与矩阵同等大小的布尔标记数组。
(如果要求O(1)空间,可以使用边界收缩法,但本题无此限制,此写法更易理解。)
运行示例
以矩阵
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
为例,模拟过程如下:
| 步骤 | 当前位置 | 方向 | 动作 |
|---|---|---|---|
| 1 | (0,0)=1 | 右 | 记录1,向右 |
| 2 | (0,1)=2 | 右 | 记录2,向右 |
| 3 | (0,2)=3 | 右 | 记录3,下一步越界 → 转向下 |
| 4 | (1,2)=6 | 下 | 记录6,向下 |
| 5 | (2,2)=9 | 下 | 记录9,下一步越界 → 转向左 |
| 6 | (2,1)=8 | 左 | 记录8,向左 |
| 7 | (2,0)=7 | 左 | 记录7,下一步越界 → 转向上 |
| 8 | (1,0)=4 | 上 | 记录4,向上 |
| 9 | (1,1)=5 | 上 | 记录5,全部遍历结束 |
最终结果:[1,2,3,6,9,8,7,4,5],符合预期。
总结
- 使用 方向数组 + 访问标记 模拟螺旋行走,逻辑直观,不易出错。
- 每次尝试前进,遇到障碍就右转,保证了螺旋顺序。
- 该解法适用于任意
m × n矩阵(包括非方阵)。