目录

题目描述

54. 螺旋矩阵

image-20230304214740996

image-20230304214747567

题意分析

输入一个 mn 列的矩阵,要求按顺时针螺旋的顺序——从左上角出发向右,沿最外圈绕行一周,再进入内圈继续绕——把所有元素依次放进一个一维列表返回。

约束给得非常小,mn 都不超过 10。这个信号很明确:本题不考效率,随便怎么写都跑得完,考的是能不能把「什么时候转弯、什么时候停」处理得一个不多、一个不少——输出长度必须恰好是 $m \cdot n$,重复和遗漏都算错。

矩阵不保证是方阵,mn 可以相差悬殊,这是全题最重要的一条题设。方阵一圈一圈剥下来,每圈四条边都完整存在;而非方阵剥到最后可能只剩一行或一列,「圈」退化成一条线段,只有去程没有回程。绝大多数错误答案都死在这个形态上。

约束同时保证 m >= 1n >= 1,矩阵不会为空,不需要判空入口。单行、单列和 1 x 1 都是合法输入,其中 1 x 1 是最小的退化用例,单行、单列则是上面说的「圈退化成线段」在整个矩阵尺度上的体现。

解法:四边界收缩

核心思路

topbottomleftright 表示尚未遍历的矩形边界。每轮依次遍历上边、右边、下边和左边,并在遍历完一条边后向内收缩对应边界;遍历下边和左边前要确认区域仍然存在,避免单行或单列重复。

解题步骤

  • 初始化四个边界,分别指向矩阵最外层。
  • 从左到右遍历上边并收缩 top,再从上到下遍历右边并收缩 right
  • 若仍有行,从右到左遍历下边并收缩 bottom
  • 若仍有列,从下到上遍历左边并收缩 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)$,不计返回结果。

关键点总结

  • 四个边界描述的是尚未遍历的区域,每走完一条边就立即收缩。
  • 遍历下边前检查 top <= bottom,遍历左边前检查 left <= right
  • 循环条件必须同时满足行区间和列区间有效。

易错点总结

  • 省略下边或左边遍历前的边界检查,会在单行、单列矩阵中重复元素。
  • 输出一条边后忘记收缩对应边界,会重复访问外圈。
  • 右边应从更新后的 top 开始,下边应从更新后的 right 开始,避免重复拐角。
  • 外层循环使用 || 会在某一维已经越界后继续访问矩阵。

相似题目

题目 难度 考察点
59. 螺旋矩阵 II 中等 反向操作:按同一条螺旋路径向空方阵里填数,因保证方阵,无需单行单列去重检查
剑指 Offer 29. 顺时针打印矩阵 简单 与本题同题换皮,但输入允许空矩阵,动手前要多一步判空
48. 旋转图像 中等 同样按圈分层处理矩阵,操作换成四点原地轮换,考坐标映射而非遍历路径