一、题目描述
给你一个 m 行 n 列的矩阵 matrix,请按照顺时针螺旋顺序,返回矩阵中的所有元素。
示例 1:
输入: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]
示例 2:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
提示:
- m == matrix.length
- n == matrix[i].length
- 1 <= m, n <= 10
- -100 <= matrix[i][j] <= 100
二、解题思路总览
核心思想:按层模拟 + 边界收缩
将矩阵看成由若干层组成,从外到内逐层遍历,每层按照「右、下、左、上」的顺序遍历。
遍历顺序说明:
第一圈:右 → 下 → 左 → 上
[[1,2,3,4],
[5,6,7,8], → [1,2,3,4] → [8,12] → [11,10,9] → [5] → 回到内层
[9,10,11,12]]
第二圈:[6,7]
| 方法 | 核心思路 | 时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 按层模拟(本题) | 每层按右、下、左、上遍历,逐层收缩边界 | O(m * n) | O(1) |
| 方向数组 | 用方向数组控制遍历方向 | O(m * n) | O(1) |
三、完整代码
cpp
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> ans;
int m = matrix.size();
int n = matrix[0].size();
int l = 0, t = 0, r = n - 1, b = m - 1;
while (1) {
// 1. 从左到右,遍历上层
for (int i = l; i <= r; i++) {
ans.push_back(matrix[t][i]);
}
if (++t > b) break;
// 2. 从上到下,遍历右列
for (int i = t; i <= b; i++) {
ans.push_back(matrix[i][r]);
}
if (--r < l) break;
// 3. 从右到左,遍历下层
for (int i = r; i >= l; i--) {
ans.push_back(matrix[b][i]);
}
if (--b < t) break;
// 4. 从下到上,遍历左列
for (int i = b; i >= t; i--) {
ans.push_back(matrix[i][l]);
}
if (++l > r) break;
}
return ans;
}
};
四、算法流程图
4.1 整体循环流程
输入:matrix
[Step 1] 初始化
l = 0, t = 0
r = n - 1
b = m - 1
ans = empty
|
v
[Step 2] 进入死循环 while(1)
|
v
[Step 3] 遍历上层(从左到右)
|
v
for i = l to r:
ans.push_back(matrix[t][i])
|
v
t++ 后判断 t > b ?
|是
v
【break 退出】 ← 循环结束
|
|否
v
[Step 4] 遍历右列(从上到下)
|
v
for i = t to b:
ans.push_back(matrix[i][r])
|
v
r-- 后判断 r < l ?
|是
v
【break 退出】 ← 循环结束
|
|否
v
[Step 5] 遍历下层(从右到左)
|
v
for i = r to l (递减):
ans.push_back(matrix[b][i])
|
v
b-- 后判断 b < t ?
|是
v
【break 退出】 ← 循环结束
|
|否
v
[Step 6] 遍历左列(从下到上)
|
v
for i = b to t (递减):
ans.push_back(matrix[i][l])
|
v
l++ 后判断 l > r ?
|是
v
【break 退出】 ← 循环结束
|
|否
v
回到 Step 2
4.2 具体矩阵执行流程
输入:[[1,2,3,4],[5,6,7,8],[9,10,11,12]]
初始:l=0, t=0, r=3, b=2
第一轮循环:
Step 1: t=0, 遍历上层 matrix[0][0..3]
→ 输出 1,2,3,4
→ t++ → t=1
→ 判断 1 > 2?不成立,继续
Step 2: r=3, 遍历右列 matrix[1..2][3]
→ 输出 matrix[1][3]=8, matrix[2][3]=12
→ r-- → r=2
→ 判断 2 < 0?不成立,继续
Step 3: b=2, 遍历下层 matrix[2][2..0]
→ 输出 matrix[2][2]=11, matrix[2][1]=10, matrix[2][0]=9
→ b-- → b=1
→ 判断 1 < 1?不成立,继续
Step 4: l=0, 遍历左列 matrix[1..1][0]
→ 输出 matrix[1][0]=5
→ l++ → l=1
→ 判断 1 > 2?不成立,继续
回到 while(1)
第二轮循环:
Step 1: t=1, 遍历上层 matrix[1][1..2]
→ 输出 matrix[1][1]=6, matrix[1][2]=7
→ t++ → t=2
→ 判断 2 > 1?成立
【break 退出循环】
输出:1,2,3,4,8,12,11,10,9,5,6,7
4.3 单行矩阵边界情况
输入:[[1,2,3,4]]
m=1, n=4
l=0, t=0, r=3, b=0
第一轮循环:
Step 1: 遍历上层 matrix[0][0..3] → 输出 1,2,3,4
t++ → t=1
判断 1 > 0(b=0)?成立 → break
输出:1,2,3,4(正确)
4.4 单列矩阵边界情况
输入:[[1],[2],[3]]
m=3, n=1
l=0, t=0, r=0, b=2
第一轮循环:
Step 1: 遍历上层 matrix[0][0] → 输出 1
t++ → t=1
判断 1 > 2?不成立
Step 2: r=0, 遍历右列 matrix[1..2][0]
→ 输出 matrix[1][0]=2, matrix[2][0]=3
r-- → r=-1
判断 -1 < 0?成立 → break
输出:1,2,3(正确)
4.5 2x2 矩阵完整流程
输入:[[1,2],[3,4]]
初始:l=0, t=0, r=1, b=1
第一轮循环:
Step 1: 输出 1,2
Step 2: 输出 4(t=1 > b=1 不成立,r=1 >= l=0 成立)
r-- → r=0
判断 0 < 0?不成立
Step 3: 输出 matrix[1][0]=3(t=1 <= b=1 成立)
b-- → b=0
判断 0 < 1?成立
Step 4: l++ → l=1,判断 1 > 0?成立 → break
输出:1,2,4,3
五、逐行解析
5.1 初始化边界变量
cpp
int l = 0, t = 0, r = n - 1, b = m - 1;
| 变量 | 含义 | 初始值 |
|---|---|---|
| l | 左边界 | 0 |
| t | 上边界 | 0 |
| r | 右边界 | n - 1 |
| b | 下边界 | m - 1 |
5.2 遍历上层
cpp
for (int i = l; i <= r; i++) {
ans.push_back(matrix[t][i]);
}
if (++t > b) break;
说明: 从左边界到右边界,遍历上边界所在行。上边界下移后,如果上边界超过下边界,说明已经遍历完所有层,退出循环。
5.3 遍历右列
cpp
for (int i = t; i <= b; i++) {
ans.push_back(matrix[i][r]);
}
if (--r < l) break;
说明: 从上边界到下边界,遍历右边界所在列。右边界左移后,如果右边界小于左边界,退出循环。
5.4 遍历下层
cpp
for (int i = r; i >= l; i--) {
ans.push_back(matrix[b][i]);
}
if (--b < t) break;
说明: 从右边界到左边界,遍历下边界所在行。下边界上移后,如果下边界小于上边界,退出循环。
5.5 遍历左列
cpp
for (int i = b; i >= t; i--) {
ans.push_back(matrix[i][l]);
}
if (++l > r) break;
说明: 从下边界到上边界,遍历左边界所在列。左边界右移后,如果左边界大于右边界,退出循环。
六、复杂度分析
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(m * n) | 每个元素恰好访问一次 |
| 空间复杂度 | O(1) | 只用四个边界指针和 ans 向量 |
七、面试追问
| 问题 | 回答要点 |
|---|---|
| 为什么要用 while(1) 而不是 while(l <= r && t <= b)? | 代码利用四个方向的 break 来控制循环,比统一的 while 条件更简洁 |
| break 的四个条件分别代表什么? | t > b:上层已遍历完;r < l:右列已遍历完;b < t:下层已遍历完;l > r:左列已遍历完 |
| 为什么单行矩阵不会重复遍历? | t++ 后判断 t > b 直接 break,第一轮循环的 Step 2-4 因 r < l 等条件被跳过 |
| 四个 for 循环的遍历方向是什么? | 上层(从左到右)、右列(从上到下)、下层(从右到左)、左列(从下到上) |
| 什么时候循环一定会退出? | 每次循环至少遍历一行或一列,边界向内收缩,最多 m+n 步必然退出 |
| 如果矩阵是 1x1 呢? | 只执行 Step 1,输出唯一元素,然后 t++ > b 触发 break |
| 进阶:能否用方向数组实现? | 可以,用 dir[4] = {0,1,2,3} 表示四个方向,每次转向用 dirIdx = (dirIdx+1)%4 |
八、相关题目
| 题号 | 题目 | 关键点 |
|---|---|---|
| 54 | 螺旋矩阵 | 本题 |
| 59 | 螺旋矩阵 II | 逆过程,根据螺旋顺序生成矩阵 |
| 885 | 螺旋矩阵 III | 起始位置和方向不同 |
| 48 | 旋转图像 | 矩阵旋转 90 度 |