LeetCode 54. 螺旋矩阵:模拟 + 边界控制详解
1. 这道题属于什么算法思想
这题主要归类为:
text
矩阵 / 模拟 / 边界控制
它不需要动态规划、贪心、回溯或图论。
题目的关键是:
text
每次按顺时针走完当前矩阵最外面的一圈,
再把未遍历区域的四条边界向内收缩。
因此需要维护:
text
top:当前未遍历区域的上边界。
bottom:当前未遍历区域的下边界。
left:当前未遍历区域的左边界。
right:当前未遍历区域的右边界。
2. 这道题到底在问什么
题目要求按照顺时针螺旋顺序,返回矩阵中的所有元素。
例如:
text
matrix =
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
顺时针螺旋顺序是:
text
1 -> 2 -> 3 -> 6 -> 9 -> 8 -> 7 -> 4 -> 5
最终返回:
text
[1, 2, 3, 6, 9, 8, 7, 4, 5]
不要把它理解成"在矩阵里转弯走路"。更稳定的理解是:
text
每次取走当前最外面一圈。
对于 3 x 3 矩阵:
text
第一圈:
1 2 3
4 6
7 8 9
第二圈:
5
3. 四条边界如何表示未遍历区域
对于 m 行、n 列矩阵,初始边界是:
text
top = 0
bottom = m - 1
left = 0
right = n - 1
例如 3 x 3 矩阵:
text
top = 0
bottom = 2
left = 0
right = 2
表示当前未遍历区域是:
text
行范围:0 到 2
列范围:0 到 2
每走完一条边,就说明那条边已经完全处理,不应该再访问,因此边界要向内收缩。
4. 每一圈固定按上、右、下、左走
当前区域由:
text
top、bottom、left、right
确定。
一圈的顺时针顺序固定为:
text
1. 上边:从左到右。
2. 右边:从上到下。
3. 下边:从右到左。
4. 左边:从下到上。
4.1 从左到右走上边
访问:
text
matrix[top][left..right]
走完后:
text
top++
因为原来的最上面一行已经处理完。
4.2 从上到下走右边
访问:
text
matrix[top..bottom][right]
注意此时 top 已经加一,因此不会重复访问右上角。
走完后:
text
right--
4.3 从右到左走下边
访问:
text
matrix[bottom][right..left]
走完后:
text
bottom--
4.4 从下到上走左边
访问:
text
matrix[bottom..top][left]
走完后:
text
left++
这样一圈结束,未遍历矩形区域缩小一层。
5. 用 3 x 3 矩阵完整推导
text
matrix =
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
初始:
text
top = 0
bottom = 2
left = 0
right = 2
result = []
第一圈:走上边
访问:
text
matrix[0][0..2] = 1, 2, 3
结果:
text
result = [1, 2, 3]
上边处理完成:
text
top = 1
第一圈:走右边
访问:
text
matrix[1..2][2] = 6, 9
结果:
text
result = [1, 2, 3, 6, 9]
右边处理完成:
text
right = 1
第一圈:走下边
访问:
text
matrix[2][1..0] = 8, 7
结果:
text
result = [1, 2, 3, 6, 9, 8, 7]
下边处理完成:
text
bottom = 1
第一圈:走左边
访问:
text
matrix[1..1][0] = 4
结果:
text
result = [1, 2, 3, 6, 9, 8, 7, 4]
左边处理完成:
text
left = 1
第一圈结束后:
text
top = 1
bottom = 1
left = 1
right = 1
只剩:
text
matrix[1][1] = 5
继续下一圈后,最终结果为:
text
[1, 2, 3, 6, 9, 8, 7, 4, 5]
6. 为什么下边和左边需要额外判断
最容易出错的是走完上边和右边后,继续走下边、左边时可能发生重复访问。
单行矩阵
text
matrix = [[1, 2, 3]]
第一步从左到右已经访问:
text
1, 2, 3
随后:
text
top = 1
bottom = 0
说明已经没有剩余行。
如果仍然执行"从右到左走下边",会把同一行重复加入结果。
因此走下边前必须判断:
java
if (top <= bottom)
单列矩阵
text
matrix =
[
[1],
[2],
[3]
]
第二步从上到下已经访问:
text
2, 3
之后:
text
left = 0
right = -1
说明已经没有剩余列。
如果仍然执行"从下到上走左边",会把同一列重复加入结果。
因此走左边前必须判断:
java
if (left <= right)
7. Java 代码完整注释
java
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
// 保存螺旋遍历结果。
List<Integer> result = new ArrayList<>();
int m = matrix.length;
int n = matrix[0].length;
// 当前未遍历区域的四条边界。
int top = 0;
int bottom = m - 1;
int left = 0;
int right = n - 1;
// 只要未遍历区域仍是一个有效矩形,就继续处理下一圈。
while (top <= bottom && left <= right) {
// 1. 从左到右遍历当前区域的上边。
for (int j = left; j <= right; j++) {
result.add(matrix[top][j]);
}
// 上边处理完成,向下收缩。
top++;
// 2. 从上到下遍历当前区域的右边。
for (int i = top; i <= bottom; i++) {
result.add(matrix[i][right]);
}
// 右边处理完成,向左收缩。
right--;
// 3. 从右到左遍历当前区域的下边。
// 单行矩阵时,上边已经处理过这一行,不能重复访问。
if (top <= bottom) {
for (int j = right; j >= left; j--) {
result.add(matrix[bottom][j]);
}
// 下边处理完成,向上收缩。
bottom--;
}
// 4. 从下到上遍历当前区域的左边。
// 单列矩阵时,右边已经处理过这一列,不能重复访问。
if (left <= right) {
for (int i = bottom; i >= top; i--) {
result.add(matrix[i][left]);
}
// 左边处理完成,向右收缩。
left++;
}
}
return result;
}
}
8. 用长方形矩阵验证
text
matrix =
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
]
第一圈:
text
上边:1, 2, 3, 4
右边:8, 12
下边:11, 10, 9
左边:5
得到:
text
[1, 2, 3, 4, 8, 12, 11, 10, 9, 5]
剩余中间区域:
text
[6, 7]
最终结果:
text
[1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]
9. 复杂度分析
矩阵中的每个元素恰好加入 result 一次。
时间复杂度:
text
O(m * n)
除了返回结果 result 外,只维护了四个边界变量。
额外空间复杂度:
text
O(1)
如果把题目要求返回的 result 也算入空间,则结果本身占用:
text
O(m * n)
10. 总结
这题属于:
text
矩阵 / 模拟 / 边界控制
每次循环按固定顺序走完当前最外圈:
text
上 -> 右 -> 下 -> 左
每走完一条边,就将对应边界向内收缩:
text
上边结束:top++
右边结束:right--
下边结束:bottom--
左边结束:left++
最容易遗漏的是:
text
走下边前检查 top <= bottom。
走左边前检查 left <= right。
它们用于防止单行、单列或最后只剩一行/一列时重复访问元素。
可以把整题记成一句话:
text
按上、右、下、左取走一圈,再向内收缩四条边界。