LeetCode 54. 螺旋矩阵
题目描述
✅ 54. 螺旋矩阵


题意分析
给定一个非空的
m × n矩阵,从左上角出发,按向右、向下、向左、向上的顺序,逐圈向内输出全部元素。矩阵不一定是正方形,每个位置必须恰好输出一次。走完外面一圈后,剩余部分仍是一个矩形,可以重复同样的过程。最后剩下的区域可能只有一行、只有一列或只有一个元素,因此不能不加判断地把四条边都再走一遍。
解法:四边界收缩
核心思路
[!blue]
用四个闭区间边界表示尚未输出的区域:行范围是
[top, bottom],列范围是[left, right]。边界外的元素已经输出,边界内的元素还没有处理。这样只需收缩范围,不需要额外的访问标记或修改原矩阵。每轮先从左到右输出上边,然后将
top加一;再沿右边从新的top走到bottom,并将right减一。由于上一条边立刻被排除,右上角不会再次访问;接下来的下边、左边也同样使用更新后的边界,避免重复各个拐角。输出下边前要检查
top <= bottom。如果已经没有剩余行,原来的上边同时也是最后一行,不能再把它当作下边重复输出。仍有行时,从新的right向左走到left,然后将bottom减一。输出左边前再检查
left <= right。如果已经没有剩余列,右边的遍历已经取完最后一列,不能再沿这列向上走。仍有列时,从新的bottom向上走到top,然后将left加一。每条边自身的循环也会检查另一维的起止位置,范围为空时不会访问元素。一圈结束后,剩余范围正好是内层矩形;每次边界收缩都排除了已经输出的整条边,因此不会重访。只要行范围和列范围都非空就继续,任意一维交错时说明已经没有剩余元素,遍历结束。
解题步骤
- 初始化
top = 0、bottom = m - 1、left = 0、right = n - 1。- 在行、列范围都有效时,输出上边
left → right,再令top++。- 输出右边
top → bottom,再令right--。- 若仍有剩余行,输出下边
right → left,再令bottom--。- 若仍有剩余列,输出左边
bottom → top,再令left++。- 对收缩后的范围重复上述过程,最后返回输出列表。
代码实现
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
int top = 0;
int bottom = matrix.length - 1;
int left = 0;
int right = matrix[0].length - 1;
List<Integer> result = new ArrayList<>(matrix.length * matrix[0].length);
while (top <= bottom && left <= right) {
for (int col = left; col <= right; col++) {
result.add(matrix[top][col]);
}
// 上边已输出,右边从新的上边界开始,避免重复拐角。
top++;
for (int row = top; row <= bottom; row++) {
result.add(matrix[row][right]);
}
right--;
// 最后可能只剩单行,收缩后需确认下边仍然存在。
if (top <= bottom) {
for (int col = right; col >= left; col--) {
result.add(matrix[bottom][col]);
}
bottom--;
}
// 同样检查剩余列范围,避免单列重复。
if (left <= right) {
for (int row = bottom; row >= top; row--) {
result.add(matrix[row][left]);
}
left++;
}
}
return result;
}
}
func spiralOrder(matrix [][]int) []int {
top, bottom := 0, len(matrix)-1
left, right := 0, len(matrix[0])-1
result := make([]int, 0, len(matrix)*len(matrix[0]))
for top <= bottom && left <= right {
for col := left; col <= right; col++ {
result = append(result, matrix[top][col])
}
// 上边已输出,右边从新的上边界开始,避免重复拐角。
top++
for row := top; row <= bottom; row++ {
result = append(result, matrix[row][right])
}
right--
// 最后可能只剩单行,收缩后需确认下边仍然存在。
if top <= bottom {
for col := right; col >= left; col-- {
result = append(result, matrix[bottom][col])
}
bottom--
}
// 同样检查剩余列范围,避免单列重复。
if left <= right {
for row := bottom; row >= top; row-- {
result = append(result, matrix[row][left])
}
left++
}
}
return result
}
复杂度分析
- 时间复杂度:$O(mn)$,每个位置恰好访问一次。
- 空间复杂度:辅助空间为 $O(1)$,只保存四个边界和循环变量;返回结果需要 $O(mn)$ 空间。
关键点总结
[!green]
- 四个边界描述的是尚未遍历的区域,每走完一条边就立即收缩。
- 遍历下边前检查
top <= bottom,遍历左边前检查left <= right。- 循环条件必须同时满足行区间和列区间有效。
易错点总结
[!yellow]
- 省略下边或左边遍历前的边界检查,会在单行、单列矩阵中重复元素。
- 输出一条边后忘记收缩对应边界,会重复访问外圈。
- 右边应从更新后的
top开始,下边应从更新后的right开始,避免重复拐角。- 外层循环使用
||会在某一维已经越界后继续访问矩阵。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 59. 螺旋矩阵 II | 中等 | 螺旋边界更新相同,本题读取矩阵,原题按顺序把数写入矩阵。 |
| 885. 螺旋矩阵 III | 中等 | 同样按方向轮转,本题逐层收缩矩形边界,原题允许走出矩阵再进入。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!