题目描述

✅ 54. 螺旋矩阵

image-20260928183828071

image-20260928183828073

题意分析

给定一个非空的 m × n 矩阵,从左上角出发,按向右、向下、向左、向上的顺序,逐圈向内输出全部元素。矩阵不一定是正方形,每个位置必须恰好输出一次。

走完外面一圈后,剩余部分仍是一个矩形,可以重复同样的过程。最后剩下的区域可能只有一行、只有一列或只有一个元素,因此不能不加判断地把四条边都再走一遍。

解法:四边界收缩

核心思路

[!blue]

用四个闭区间边界表示尚未输出的区域:行范围是 [top, bottom],列范围是 [left, right]。边界外的元素已经输出,边界内的元素还没有处理。这样只需收缩范围,不需要额外的访问标记或修改原矩阵。

每轮先从左到右输出上边,然后将 top 加一;再沿右边从新的 top 走到 bottom,并将 right 减一。由于上一条边立刻被排除,右上角不会再次访问;接下来的下边、左边也同样使用更新后的边界,避免重复各个拐角。

输出下边前要检查 top <= bottom。如果已经没有剩余行,原来的上边同时也是最后一行,不能再把它当作下边重复输出。仍有行时,从新的 right 向左走到 left,然后将 bottom 减一。

输出左边前再检查 left <= right。如果已经没有剩余列,右边的遍历已经取完最后一列,不能再沿这列向上走。仍有列时,从新的 bottom 向上走到 top,然后将 left 加一。每条边自身的循环也会检查另一维的起止位置,范围为空时不会访问元素。

一圈结束后,剩余范围正好是内层矩形;每次边界收缩都排除了已经输出的整条边,因此不会重访。只要行范围和列范围都非空就继续,任意一维交错时说明已经没有剩余元素,遍历结束。

解题步骤

  1. 初始化 top = 0、bottom = m - 1、left = 0、right = n - 1。
  2. 在行、列范围都有效时,输出上边 left → right,再令 top++。
  3. 输出右边 top → bottom,再令 right--。
  4. 若仍有剩余行,输出下边 right → left,再令 bottom--。
  5. 若仍有剩余列,输出左边 bottom → top,再令 left++。
  6. 对收缩后的范围重复上述过程,最后返回输出列表。

代码实现

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 中等 同样按方向轮转,本题逐层收缩矩形边界,原题允许走出矩阵再进入。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/76110162
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!