🌀 题目链接:leetcode.cn/problems/spiral-matrix/
🔍 难度:中等 | 🏷️ 标签:数组、矩阵、模拟、双指针、层序遍历
⏱️ 目标时间复杂度:O(m×n)
💾 空间复杂度:O(1)(最优解) / O(m×n)(辅助空间)
🎯 题目分析
给定一个 m × n 的二维矩阵,要求按照 顺时针螺旋顺序 输出所有元素。
例如:
text
输入:
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
输出:[1,2,3,6,9,8,7,4,5]
观察发现,路径是从左上角开始,按"右→下→左→上"的方向循环前进,遇到边界或已访问位置时转向。
这道题是典型的 "路径模拟"问题 ,核心在于如何控制移动方向与边界判断。
它在面试中常作为考察 逻辑思维能力、边界处理能力 的经典题目,尤其适合评估候选人对"状态转移"和"循环控制"的掌握程度。
✅ 核心算法及代码讲解
🧠 方法一:模拟法(使用 visited 数组)
这是最直观的解法,通过维护一个 visited 布尔矩阵来记录每个格子是否已被访问,并用方向数组控制移动方向。
🛠️ 关键点:
- 使用四个方向向量
{ {0,1}, {1,0}, {0,-1}, {-1,0} }表示:右 → 下 → 左 → 上。 - 当下一步越界或已访问,则顺时针旋转方向(即
(directionIndex + 1) % 4)。 - 每次走一步,更新当前位置并标记为已访问。
💡 优点:
- 思路清晰,容易理解。
- 适用于任意形状的网格路径模拟。
⚠️ 缺点:
- 需要额外
O(mn)空间存储visited,不够高效。
cpp
class Solution {
private:
static constexpr int directions[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右、下、左、上
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (matrix.size() == 0 || matrix[0].size() == 0) {
return {};
}
int rows = matrix.size(), columns = matrix[0].size();
vector<vector<bool>> visited(rows, vector<bool>(columns)); // 访问标记
int total = rows * columns;
vector<int> order(total);
int row = 0, column = 0;
int directionIndex = 0; // 当前方向索引
for (int i = 0; i < total; i++) {
order[i] = matrix[row][column]; // 收集当前值
visited[row][column] = true; // 标记已访问
// 计算下一个位置
int nextRow = row + directions[directionIndex][0];
int nextColumn = column + directions[directionIndex][1];
// 判断是否需要转向:越界 或 已访问
if (nextRow < 0 || nextRow >= rows || nextColumn < 0 || nextColumn >= columns || visited[nextRow][nextColumn]) {
directionIndex = (directionIndex + 1) % 4; // 顺时针转
}
// 更新位置
row += directions[directionIndex][0];
column += directions[directionIndex][1];
}
return order;
}
};
✅ 时间复杂度:O(mn)
❌ 空间复杂度:O(mn) ------ 因为用了
visited数组
🧠 方法二:按层模拟(推荐!面试首选)✅
将整个矩阵看作若干"层",每层从外到内依次遍历。
🧩 分层思想:
- 第一层:外圈,从
[top][left]到[bottom][right] - 第二层:内圈,缩小边界后继续
- 直至
left > right或top > bottom结束
🔄 遍历顺序:
- 上行 :从左到右 →
(top, left)到(top, right) - 右列 :从上到下 →
(top+1, right)到(bottom, right) - 下行 :从右到左 →
(bottom, right-1)到(bottom, left+1)(仅当left < right) - 左列 :从下到上 →
(bottom-1, left)到(top+1, left)(仅当top < bottom)
✅ 优势:
- 不需要额外空间,空间复杂度为 O(1)(不计输出数组)
- 边界条件明确,代码简洁易读
- 是面试官最爱的"优雅解法"
cpp
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (matrix.size() == 0 || matrix[0].size() == 0) {
return {};
}
int rows = matrix.size(), columns = matrix[0].size();
vector<int> order;
int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
while (left <= right && top <= bottom) {
// 1. 上行:从左到右
for (int column = left; column <= right; column++) {
order.push_back(matrix[top][column]);
}
top++; // 上边界下移
// 2. 右列:从上到下
for (int row = top; row <= bottom; row++) {
order.push_back(matrix[row][right]);
}
right--; // 右边界左移
// 3. 下行:从右到左(需确保有下一行且不是单行)
if (left < right) {
for (int column = right; column > left; column--) {
order.push_back(matrix[bottom][column]);
}
}
bottom--; // 下边界上移
// 4. 左列:从下到上(需确保有左列且不是单列)
if (top < bottom) {
for (int row = bottom; row >= top; row--) {
order.push_back(matrix[row][left]);
}
}
left++; // 左边界右移
}
return order;
}
};
✅ 时间复杂度:O(mn)
✅ 空间复杂度:O(1) ------ 仅用常数变量
🧭 解题思路(分步详解)
📌 步骤 1:初始化边界
cpp
int left = 0, right = cols - 1, top = 0, bottom = rows - 1;
定义四个指针分别指向当前层的四条边。
📌 步骤 2:循环遍历每一层
cpp
while (left <= right && top <= bottom)
只要还有未遍历的区域就继续。
📌 步骤 3:逐层遍历
- 上行 :
top行从left到right - 右列 :
right列从top+1到bottom - 下行 :
bottom行从right-1到left+1(避免重复) - 左列 :
left列从bottom-1到top+1(避免重复)
🚫 注意:第3、4步必须加条件判断,防止在 1xN 或 Nx1 的情况下重复添加元素!
📌 步骤 4:收缩边界
每次遍历完一层后:
cpp
left++, right--, top++, bottom--;
直到无法再进入新层为止。
📊 算法分析
| 方法 | 时间复杂度 | 空间复杂度 | 是否推荐 |
|---|---|---|---|
| 模拟法(带 visited) | O(mn) | O(mn) | ❌ 一般情况不推荐 |
| 按层模拟 | O(mn) | O(1) | ✅ 推荐,面试首选 |
🔍 为什么按层更优?
- 面试中追求 空间效率 和 代码优雅性
- 能体现你对"结构化思维"的理解
- 更容易扩展到其他类似问题(如"蛇形填充"、"Z 字形打印"等)
💻 完整代码(模板格式)
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (matrix.size() == 0 || matrix[0].size() == 0) {
return {};
}
int rows = matrix.size(), columns = matrix[0].size();
vector<int> order;
int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
while (left <= right && top <= bottom) {
// 1. 上行:从左到右
for (int column = left; column <= right; column++) {
order.push_back(matrix[top][column]);
}
top++;
// 2. 右列:从上到下
for (int row = top; row <= bottom; row++) {
order.push_back(matrix[row][right]);
}
right--;
// 3. 下行:从右到左(仅当不是单行)
if (left < right) {
for (int column = right; column > left; column--) {
order.push_back(matrix[bottom][column]);
}
}
bottom--;
// 4. 左列:从下到上(仅当不是单列)
if (top < bottom) {
for (int row = bottom; row >= top; row--) {
order.push_back(matrix[row][left]);
}
}
left++;
}
return order;
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// 示例测试用例
vector<vector<int>> matrix1 = {{1,2,3},{4,5,6},{7,8,9}};
vector<vector<int>> matrix2 = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
Solution sol;
auto result1 = sol.spiralOrder(matrix1);
auto result2 = sol.spiralOrder(matrix2);
cout << "Test 1: ";
for (auto x : result1) cout << x << " ";
cout << endl;
cout << "Test 2: ";
for (auto x : result2) cout << x << " ";
cout << endl;
return 0;
}
✅ 输出结果:
yaml
Test 1: 1 2 3 6 9 8 7 4 5
Test 2: 1 2 3 4 8 12 11 10 9 5 6 7
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📣 下一期预告:LeetCode 热题 100 第48题 ------ 旋转图像(中等)
🔹 题目 :给你一个
n×n的二维矩阵,将其顺时针旋转 90 度。要求 原地修改,不能使用额外的矩阵。🔹 核心思路:利用"对角线翻转 + 水平翻转"或"分圈旋转"策略,实现 O(1) 空间原地旋转。
🔹 考点:矩阵操作、原地算法、数学变换、边界处理。
🔹 难度:中等,但非常典型,常出现在大厂面试中(如字节、腾讯、阿里)。
🔹 提示:
- 不能直接复制再赋值!
- 尝试找规律:
(i,j)→(j,n-1-i),可推导出旋转公式。- 推荐方法:先沿主对角线转置,再水平翻转。
💡 提示:记住这个口诀:"转置 + 翻转 = 旋转"!
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!