主题
总体思路

根据题意,我们从左上角
- 首先向右走,如果到达矩阵边界,则向右转
,前进方向变为向下。 - 然后向下走,如果到达矩阵边界,则向右转
,前进方向变为向左。 - 然后向左走,如果到达矩阵边界,则向右转
,前进方向变为向上。 - 然后向上走,先从
走到 ,然后从 准备向上走,但上面的 是一个已经访问过的数字,那么向右转 ,前进方向变为向右。 - 重复上述过程,直到答案的长度为
。
方法一:标记
- 对于已经访问过的数字,可将其标记为
或者空,从而避免重复访问。 - 用一个长为
的方向数组 分别表示右下左上 个方向。同时用一个下标 表示当前方向,初始值为 ,表示一开始向右。 - 每次移动,相当于把行号增加
,把列号增加 。 - 向右转
,相当于把 增加 ,但在 时要回到 。两种情况合二为一,把 更新为 。
python
DIRS = (0, 1), (1, 0), (0, -1), (-1, 0) # 右下左上
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
m, n = len(matrix), len(matrix[0])
ans = []
i = j = di = 0
for _ in range(m * n): # 一共走 mn 步
ans.append(matrix[i][j])
matrix[i][j] = None # 标记,表示已经访问过(已经加入答案)
x, y = i + DIRS[di][0], j + DIRS[di][1] # 下一步的位置
# 如果 (x, y) 出界或者已经访问过
if x < 0 or x >= m or y < 0 or y >= n or matrix[x][y] is None:
di = (di + 1) % 4 # 右转 90°
i += DIRS[di][0]
j += DIRS[di][1] # 走一步
return anscpp
// C++ 版待补充cpp
class Solution {
static constexpr int DIRS[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<int> ans(m * n);
int i = 0, j = 0, di = 0;
for (int k = 0; k < m * n; k++) { // 一共走 mn 步
ans[k] = matrix[i][j];
matrix[i][j] = INT_MAX; // 标记,表示已经访问过(已经加入答案)
int x = i + DIRS[di][0];
int y = j + DIRS[di][1]; // 下一步的位置
// 如果 (x, y) 出界或者已经访问过
if (x < 0 || x >= m || y < 0 || y >= n || matrix[x][y] == INT_MAX) {
di = (di + 1) % 4; // 右转 90°
}
i += DIRS[di][0];
j += DIRS[di][1]; // 走一步
}
return ans;
}
};复杂度分析
- 时间复杂度:
,其中 和 分别为 的行数和列数。 - 空间复杂度:
。返回值不计入。
方法二:不标记
上面的做法需要修改

示例 2 这
其中第
其中第
由于走的步数是有规律的,我们可以精确地控制在每个方向上要走多少步,无需判断是否出界、是否重复访问:
- 从
开始。 - 一开始,向右走
步,每次先走一步,再把数字加入答案。走 步即 ,矩阵第一排的数都加入了答案。 - 然后向下走
步,即 。 - 然后向左走
步,即 。 - 然后向上走
步,即 。 - 然后向右走
步,即 。 - 重复上述过程,直到答案的长度等于
。
代码实现时,可以这样简化代码:
- 一开始走
步。 - 把
分别更新为 ,这样下一轮循环又可以走 步(相当于走了 步),无需修改其他逻辑。相当于撕掉矩阵的第一行,把矩阵旋转 后,对这个 行 列的新矩阵继续做同样的操作。 - 把
分别更新为 ,这样下一轮循环又可以走 步(相当于走了 步)。 - 把
分别更新为 ,这样下一轮循环又可以走 步(相当于走了 步)。 - 依此类推,每次只需把
分别更新为 即可。
答疑
问:如何说明这个规律的正确性?
答:在上文的例子中,我们把示例 2 分成了
python
DIRS = (0, 1), (1, 0), (0, -1), (-1, 0) # 右下左上
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
m, n = len(matrix), len(matrix[0])
size = m * n
ans = []
i, j, di = 0, -1, 0 # 从 (0, -1) 开始
while len(ans) < size:
dx, dy = DIRS[di]
for _ in range(n): # 走 n 步(注意 n 会减少)
i += dx
j += dy # 先走一步
ans.append(matrix[i][j]) # 再加入答案
di = (di + 1) % 4 # 右转 90°
m, n = n, m - 1 # 减少后面的循环次数(步数)
return anscpp
// C++ 版待补充cpp
class Solution {
static constexpr int DIRS[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
int size = m * n;
vector<int> ans;
int i = 0, j = -1; // 从 (0, -1) 开始
for (int di = 0; ans.size() < size; di = (di + 1) % 4) {
for (int k = 0; k < n; k++) { // 走 n 步(注意 n 会减少)
i += DIRS[di][0];
j += DIRS[di][1]; // 先走一步
ans.push_back(matrix[i][j]); // 再加入答案
}
m--; // 减少后面的循环次数(步数)
swap(n, m);
}
return ans;
}
};复杂度分析
- 时间复杂度:
,其中 和 分别为 的行数和列数。 - 空间复杂度:
。返回值不计入。
思考题
假如
输出走了
你能用
欢迎在评论区分享你的思路/代码。
相似题目(数字表示难度分)
- 1041. 困于环中的机器人 1521
- 874. 模拟行走机器人 1846
- 2069. 模拟行走机器人 II 1919
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府