题目
给你一个 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 预先定义顺时针 4 个行走顺序:右 → 下 → 左 → 上
dx = {0, 1, 0, -1}
dy = {1, 0, -1, 0}
t=0:向右;t=1:向下;t=2:向左;t=3:向上
t=(t+1)%4 实现循环切换方向
- 预判下一步 拿到当前方向,先算下一步坐标
nx, ny
- 如果下一步越界,或者已经访问过 → 顺时针更换方向
- 换完方向之后,再真正移动坐标 x,y
- 访问标记
- 标准写法:开辟 vis 布尔数组标记,不破坏输入
- 终止条件 一共 m_n 个元素,答案数组收集满 m_n 个元素,循环结束。
本质:迷宫行走模拟,不需要维护上下左右四个边界,不用处理各种边界 break,一套逻辑兼容方阵、单行、单列、长方形矩阵。
代码
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
int m = matrix.size();
int n = matrix[0].size();
vector<vector<bool>> vis(m, vector<bool>(n, false));
vector<int> ans;
int t = 0;
int x = 0, y = 0;
while(ans.size() < m * n) {
ans.push_back(matrix[x][y]);
vis[x][y] = true;
int nx = x + dx[t];
int ny = y + dy[t];
if(nx < 0 || nx >= m || ny < 0 || ny >= n || vis[nx][ny]) {
t = (t + 1) % 4;
}
x += dx[t];
y += dy[t];
}
return ans;
}
};
时间复杂度: 空间复杂度: 不开修改原数组,额外开一个
vis[m][n]布尔数组记录访问,空间。
代码(空间优化)
由于-100 <= matrix[i][j] <= 100,并且最后只要返回所有矩阵元素,所以我们可以将原数组当作标记数组使用,也就是:
- 原地赋值 200 标记已经走过(修改原矩阵)
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
int m = matrix.size();
int n = matrix[0].size();
vector<int> ans;
int t = 0;
int x = 0, y = 0;
while(ans.size() < n * m) {
ans.push_back(matrix[x][y]);
matrix[x][y] = 200;
int nx = x + dx[t];
int ny = y + dy[t];
if(nx < 0 || nx >= m || ny < 0 || ny >=n || matrix[nx][ny] == 200) {
t = (t + 1) % 4;
}
x += dx[t];
y += dy[t];
}
return ans;
}
};
时间复杂度: 空间复杂度:
按层模拟(边界收缩)
核心思想
把矩阵看成一层一层的环形圈层,从最外层向内一层层剥离、遍历,每一层都是一个顺时针的环。
四个边界变量
top:当前层的上边界行bottom:当前层的下边界行left:当前层的左边界列right:当前层的右边界列
每一层分 4 段遍历:
- 上边框:
top行,从左到右left → right;遍历完,这一层上边处理完毕,top++(上边界向内收缩) - 右边框:
right列,从上到下top → bottom;遍历完,right--(右边界向内收缩) - 下边框:
bottom行,从右到左right → left;遍历完,bottom--(下边界向内收缩) - 左边框:
left列,从下到上bottom → top;遍历完,left++(左边界向内收缩)
关键终止判断
每走完一条边,立刻检查边界是否交叉:
- 如果
top > bottom或者left > right,说明已经没有元素可以遍历,直接退出循环。
为什么要每一步判断? 当矩阵不是方阵(比如单行、单列、长方形),向内收缩后,可能只剩下一行 / 一列,此时不需要再走后面的边框,继续走会造成重复读取元素。
代码
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
if(matrix.empty()) return res;
int top = 0;
int bottom = matrix.size() - 1;
int left = 0;
int right = matrix[0].size() - 1;
while(true) {
for(int i = left; i <= right; i++) res.push_back(matrix[top][i]);
top++;
if(top > bottom) break;
for(int i = top; i <= bottom; i++) res.push_back(matrix[i][right]);
right--;
if(right < left) break;
for(int i = right; i >= left; i--) res.push_back(matrix[bottom][i]);
bottom--;
if(bottom < top) break;
for(int i = bottom; i >= top; i--) res.push_back(matrix[i][left]);
left++;
if(left > right) break;
}
return res;
}
};
时间复杂度:,每个元素只访问一次 空间复杂度:,除输出数组,只用 4 个边界变量,不修改原矩阵。
算法对比
| 对比维度 | 按层模拟(边界收缩) | 方向数组模拟行走 |
|---|---|---|
| 核心思想 | 把矩阵看作多层圆环,逐层向内剥离,收缩四条边界 | 模拟坐标一步步行走,碰壁 / 遇到已访问元素就顺时针换向 |
| 额外空间 (不计输出 ans) | O(1),只用 4 个边界变量 | O(mn),需要 vis 标记数组;原地标记会修改输入矩阵 |
| 是否修改原输入 | 不修改 | 原地标记版本会修改原矩阵;vis 版本不修改,但耗内存 |
| 逻辑流程 | 4 段遍历,每遍历一条边收缩边界,每步后判断边界交叉退出 | 循环移动坐标,预判下一步,碰壁就切换方向,收集满m∗n个元素结束 |
| 特殊场景处理 | 需要每段后判断边界,否则单行 / 单列会重复输出 | 统一逻辑,天然兼容方阵、单行、单列、长方形,不用额外特殊判断 |
| 代码易错点 | 容易忘记每一步后的 break 判断,边界交叉漏写 | 容易搞错:先预判下一步,换向之后再移动坐标;循环条件不能写错 |
| 面试评价 | ⭐⭐⭐⭐⭐ 最优解。O (1) 空间,面试官期望的标准解法 | ⭐⭐⭐⭐ 思路直观,适合笔试快速写对;但空间开销大,要主动说明缺点 |
| 时间复杂度 | O(mn),每个元素访问 1 次 | O(mn),每个元素访问 1 次 |

