本文概览:本文以LeetCode经典题目"螺旋矩阵"为例,重点讲解如何将螺旋遍历过程拆解为四条边的循环收缩,通过边界变量动态控制遍历范围和退出条件


一、题目

螺旋矩阵题目

二、题目分析

给定一个 m×n 矩阵,按照顺时针螺旋顺序返回所有元素

目标:模拟从外圈到内圈的螺旋遍历过程

核心难点:这题没有暴力与优化的区分,唯一的解法就是模拟螺旋遍历。难点在于:

  1. 如何正确模拟这个遍历过程
  2. 如何找到退出条件

思路概览

Java实现代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public List<Integer> spiralOrder(int[][] matrix) {
// 各边界
int left = 0;
int top = 0;
int right = matrix[0].length - 1;
int bottom = matrix.length - 1;

// 结果列表
Integer[] res = new Integer[(right + 1) * (bottom + 1)];

// 结果索引
int index = 0;

// 遍历矩阵
while (true) {
// 从左到右遍历顶层
for (int i = left; i <= right; i++) {
res[index++] = matrix[top][i];
}
if (++top > bottom) break;
// 从上到下遍历右层
for (int i = top; i <= bottom; i++) {
res[index++] = matrix[i][right];
}
if (--right < left) break;
// 从右到左遍历下层
for (int i = right; i >= left; i--) {
res[index++] = matrix[bottom][i];
}
if (--bottom < top) break;
// 从下到上遍历左层
for (int i = bottom; i >= top; i--) {
res[index++] = matrix[i][left];
}
if (++left > right) break;
}
// 返回结果列表
return new ArrayList<>(List.of(res));
}

思路简要说明

  1. 四条边:将螺旋遍历拆解为四条边——顶层(左→右)、右层(上→下)、底层(右→左)、左层(下→上)

  2. 边界收缩:每遍历完一条边,对应的边界就往内缩一次(top++、right--、bottom--、left++)

  3. 退出条件:每次收缩后检查边界是否交叉,交叉说明遍历完毕

三、思路详解

螺旋遍历的本质

仔细观察螺旋遍历的过程,它其实就是一个不断"剥圈"的过程——每一圈由四条边组成,遍历完一圈后往内缩一圈,继续遍历下一圈

我们可以把每一圈拆解为四条边,按固定顺序遍历:

1
2
3
4
① 顶层:从左到右 →→→→→
② 右层:从上到下 ↓↓↓↓↓
③ 底层:从右到左 ←←←←←
④ 左层:从下到上 ↑↑↑↑↑

用一个 4×5 的矩阵来直观感受:

1
2
3
4
5
→→→→→  ← 顶层(第1条边)
↑ ↓
↑ ↓ ← 右层(第2条边)
↑ ↓
←←←←← ← 底层(第3条边)+ 左层(第4条边)

关键规律:每遍历完一条边,边界就收缩

遍历完顶层后,顶层已经走过了,下一次的顶层应该往下移一行(top++

遍历完右层后,右层已经走过了,下一次的右层应该往左移一列(right--

遍历完底层后,底层已经走过了,下一次的底层应该往上移一行(bottom--

遍历完左层后,左层已经走过了,下一次的左层应该往右移一列(left++

用图来表示边界的收缩过程:

初始状态

1
2
3
4
5
6
left         right
↓ ↓
1 2 3 4 5 ← top
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20 ← bottom

遍历顶层 1,2,3,4,5 后,top++

1
2
3
4
5
6
left         right
↓ ↓
-- -- -- -- -- ← top 下移
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20 ← bottom

遍历右层 10,15,20 后,right--

1
2
3
4
5
6
left      right
↓ ↓
-- -- -- -- --
6 7 8 9 | ← right 左移
11 12 13 14 |
16 17 18 19 | ← bottom

遍历底层 19,18,17,16 后,bottom--

1
2
3
4
5
6
left      right
↓ ↓
-- -- -- -- --
6 7 8 9 |
11 12 13 14 | ← bottom 上移
-- -- -- -- |

遍历左层 11,6 后,left++

1
2
3
4
5
6
7
 left   right
↓ ↓
-- -- -- -- --
| 7 8 9 |
| 12 13 14 |
-- -- -- -- --
↑ left 右移

此时进入内圈,继续重复四条边的遍历,直到边界交叉

退出条件

每次边界收缩后,需要检查边界是否交叉:

  • top > bottom:顶层超过了底层,说明纵向没有剩余行
  • right < left:右层超过了左层,说明横向没有剩余列

只要出现任意一种交叉,就说明所有元素已经遍历完毕,退出循环

注意:退出条件必须在每条边遍历完后立即检查,而不是四条边全部走完再检查。因为矩阵不一定是正方形,可能在某一条边走完后就已经遍历完了。比如一个 1×3 的矩阵 [1,2,3],遍历完顶层后 top++ 就已经 top > bottom 了,此时应该立即退出,不能再走右层、底层、左层

举例说明

matrix = [[1,2,3],[4,5,6],[7,8,9]] 为例

初始left=0, top=0, right=2, bottom=2

步骤 遍历边 路径 收缩 边界
1 顶层 左→右 1,2,3 top++ → top=1 top≤bottom,继续
2 右层 上→下 6,9 right-- → right=1 right≥left,继续
3 底层 右→左 8,7 bottom-- → bottom=1 bottom≥top,继续
4 左层 下→上 4 left++ → left=1 left≤right,继续

第二轮left=1, top=1, right=1, bottom=1

步骤 遍历边 路径 收缩 边界
5 顶层 左→右 5 top++ → top=2 top>bottom,退出!

最终结果为 [1,2,3,6,9,8,7,4,5]

  • 时间复杂度:O(mn),每个元素遍历一次
  • 空间复杂度:O(1),只用了四个边界变量(结果数组不算额外空间)