本文最后更新于 2026-01-06,文章内容可能已经过时。

「螺旋矩阵」是 LeetCode 中经典的中等难度题目,核心考察矩阵遍历的边界控制循环逻辑设计。这道题的关键痛点是如何按 右→下→左→上 的螺旋顺序,不重复、不遗漏地遍历矩阵,同时灵活处理不同行数和列数的矩阵(如单行、单列矩阵)。

实际开发中,类似矩阵数据序列化图形界面元素遍历等场景,都能用到它的核心逻辑。今天我们从最直观的暴力解法出发,一步步优化到逻辑清晰、边界处理完善的最优解,带你吃透螺旋遍历的设计精髓~

📌 题目重述

给你一个 m x n 的整数矩阵 matrix,请你按照顺时针螺旋顺序,返回矩阵中所有元素组成的列表。

螺旋顺序的规则:

  1. 从矩阵左上角开始,向右遍历当前行;

  2. 到达当前行最右侧后,向下遍历当前列;

  3. 到达当前列最下方后,向左遍历当前行(注意:当前行需未被遍历过);

  4. 到达当前行最左侧后,向上遍历当前列(注意:当前列需未被遍历过);

  5. 重复上述步骤,直到所有元素都被遍历。

举个例子:

矩阵螺旋遍历示例

1 示例 1 (3×3矩阵):

1
2
3
4
5
6
7
8
9
输出: [1, 2, 3, 6, 9, 8, 7, 4, 5]

遍历顺序:

2 示例 2 (4×4矩阵):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
输出: [1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10]

遍历顺序:

🚶 阶梯思路拆解

第一步:暴力思路(标记矩阵 + 方向数组)🥾

最直观的想法是,用一个标记矩阵记录哪些元素已经被遍历过,再用一个方向数组控制遍历方向(右→下→左→上),遇到边界或已遍历元素时切换方向,直到所有元素都被遍历。这种方法逻辑简单,容易理解,但需要额外的空间存储标记矩阵。

💡 核心逻辑

  1. 初始化标记矩阵 visited( m x n),所有元素初始为 false(未遍历);

  2. 定义方向数组 dirs,表示 右、下、左、上 四个方向的行、列偏移量: dirs = [[0,1], [1,0], [0,-1], [-1,0]];

  3. 初始化当前位置 (row, col) = (0, 0),当前方向索引 dirIdx = 0(初始方向为右);

  4. 循环遍历 m*n 次(矩阵总元素数):

    • 将当前元素 matrix[row][col] 加入结果列表,标记 visited[row][col] = true;

    • 计算下一个位置 (nextRow, nextCol) = (row + dirs[dirIdx][0], col + dirs[dirIdx][1]);

    • 若下一个位置超出矩阵边界,或已被遍历( visited[nextRow][nextCol] = true),切换方向( dirIdx = (dirIdx + 1) % 4);

    • 更新当前位置为新的方向对应的下一个位置;

  5. 遍历结束,返回结果列表。

✅ 代码实现(Java)

import java.util.ArrayList;
import java.util.List;

public class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> result = new ArrayList<>();
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return result;
        }

        int m = matrix.length;
        int n = matrix[0].length;
        boolean[][] visited = new boolean[m][n];
        // 方向数组:右、下、左、上
        int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        int row = 0, col = 0;
        int dirIdx = 0; // 当前方向索引

        for (int i = 0; i < m * n; i++) {
            // 加入当前元素,标记已遍历
            result.add(matrix[row][col]);
            visited[row][col] = true;

            // 计算下一个位置
            int nextRow = row + dirs[dirIdx][0];
            int nextCol = col + dirs[dirIdx][1];

            // 下一个位置无效(越界或已遍历),切换方向
            if (nextRow < 0 || nextRow >= m || nextCol < 0 || nextCol >= n || visited[nextRow][nextCol]) {
                dirIdx = (dirIdx + 1) % 4;
            }

            // 更新当前位置
            row += dirs[dirIdx][0];
            col += dirs[dirIdx][1];
        }

        return result;
    }
}

⚙️ 复杂度分析

复杂度类型

计算结果

说明

时间复杂度

O(mn)

遍历矩阵所有元素一次,每个元素处理 O (1),整体线性时间

空间复杂度

O(mn)

标记矩阵占用 O (mn) 空间,方向数组和结果列表不计入额外空间(结果列表是必须输出的)

🚫 遇到的问题

空间开销过大!当矩阵规模为 10⁴ x 10⁴ 时,标记矩阵会占用大量内存。核心问题是:我们不需要单独记录每个元素是否被遍历,矩阵的边界本身就可以作为遍历的限制条件—— 通过收缩边界,就能实现无重复、无遗漏的遍历。

第二步:优化思路(边界收缩法)🗺️

核心想法是:用四个变量标记矩阵的边界(上、下、左、右),按右→下→左→上的顺序遍历,每遍历完一行或一列就收缩对应的边界,直到边界交叉(所有元素遍历完毕)。这种方法无需额外标记矩阵,空间复杂度优化到 O (1)(除结果列表外)。

💡 核心逻辑

  1. 定义四个边界变量:

    • top:当前未遍历区域的上边界(初始为 0);

    • bottom:当前未遍历区域的下边界(初始为 m-1);

    • left:当前未遍历区域的左边界(初始为 0);

    • right:当前未遍历区域的右边界(初始为 n-1);

  2. 循环遍历,直到 top > bottom 或 left > right(所有元素遍历完毕):

    • 右遍历:从左到右遍历当前上边界 top 行,遍历完后 top++(上边界收缩);

    • 下遍历:从上到下遍历当前右边界 right 列,遍历完后 right--(右边界收缩);

    • 左遍历:若 top <= bottom(避免重复遍历单行矩阵),从右到左遍历当前下边界 bottom 行,遍历完后 bottom--(下边界收缩);

    • 上遍历:若 left <= right(避免重复遍历单列矩阵),从下到上遍历当前左边界 left 列,遍历完后 left++(左边界收缩);

  3. 遍历结束,返回结果列表。

✅ 代码实现(Java,最优解)

import java.util.ArrayList;
import java.util.List;

public class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> result = new ArrayList<>();
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return result;
        }

        int m = matrix.length;
        int n = matrix[0].length;
        int top = 0;    // 上边界
        int bottom = m - 1; // 下边界
        int left = 0;   // 左边界
        int right = n - 1;  // 右边界

        while (true) {
            // 1. 右遍历:上边界行,从左到右
            for (int col = left; col <= right; col++) {
                result.add(matrix[top][col]);
            }
            top++; // 上边界收缩
            if (top > bottom) break;

            // 2. 下遍历:右边界列,从上到下
            for (int row = top; row <= bottom; row++) {
                result.add(matrix[row][right]);
            }
            right--; // 右边界收缩
            if (left > right) break;

            // 3. 左遍历:下边界行,从右到左(避免单行重复遍历)
            if (top <= bottom) {
                for (int col = right; col >= left; col--) {
                    result.add(matrix[bottom][col]);
                }
                bottom--; // 下边界收缩
            }
            if (top > bottom) break;  // 检查上下是否越界

            // 4. 上遍历:左边界列,从下到上(避免单列重复遍历)
            if (left <= right) {
                for (int row = bottom; row >= top; row--) {
                    result.add(matrix[row][left]);
                }
                left++; // 左边界收缩
            }
            if (left > right) break;  // 检查左右是否越界
        }

        return result;
    }
}

⚙️ 复杂度分析

复杂度类型

计算结果

说明

时间复杂度

O(mn)

遍历矩阵所有元素一次,每个元素处理 O (1),整体线性时间

空间复杂度

O(1)

仅使用四个边界变量和循环变量,无额外空间开销(结果列表是必须输出的)

✨ 核心优势

  1. 时间最优:O (mn) 时间复杂度,与暴力解法一致,但逻辑更清晰;

  2. 空间最优:O (1) 额外空间复杂度,无需标记矩阵,大幅节省内存;

  3. 边界处理完善:通过 top <= bottom 和 left <= right 的判断,完美处理单行、单列、矩形矩阵等所有场景;

  4. 扩展性强:循环逻辑固定,可轻松适配不同规模的矩阵。

第三步:边界情况与易错点

常见边界场景

  1. 单行矩阵(如 [[1,2,3,4]]):仅执行右遍历,结果为 [1,2,3,4];

  2. 单列矩阵(如 [[1],[2],[3]]):执行右遍历(单行)→下遍历(无)→左遍历(跳过)→上遍历,结果为 [1,2,3];

  3. 1x1 矩阵(如 [[5]]):仅执行右遍历,结果为 [5];

  4. 矩形矩阵(如 2x5 矩阵):按右→下→左→上循环,边界收缩顺畅,无重复遍历。

易错点提醒

  • 遗漏边界判断:未判断 top <= bottom 或 left <= right,导致单行 / 单列矩阵被重复遍历(如单行矩阵右遍历后又左遍历);

  • 边界收缩顺序错误:遍历完一行 / 列后未及时收缩边界,导致元素重复遍历;

  • 循环终止条件缺失:未在每次边界收缩后判断是否交叉,导致循环无限执行;

  • 矩阵空判断:未处理 matrix 为 null、行数为 0 或列数为 0 的情况,导致空指针异常。

📝 总结

「螺旋矩阵」的解题核心是边界控制与循环逻辑设计,从暴力到最优的递进思路清晰:

  1. 暴力解法:标记矩阵 + 方向数组,直观但空间 O (mn);

  2. 边界收缩法:利用四个边界变量控制遍历范围,空间 O (1),边界处理完善,是最优解。

关键技巧

  • 边界定义:用 top、bottom、left、right 四个变量框定未遍历区域,遍历一行 / 列就收缩对应边界;

  • 循环顺序:固定右→下→左→上的遍历顺序,确保螺旋方向正确;

  • 边界判断:在左遍历和上遍历前添加条件判断,避免单行 / 单列矩阵重复遍历;

  • 终止条件:每次边界收缩后判断是否交叉,及时终止循环。

同类题扩展建议

掌握了这道题的思路后,可以尝试这些进阶题目,都是同一类边界控制思维的应用:

  1. LeetCode 59. 螺旋矩阵 II:与本题相反,根据给定的 n,生成 n x n 的螺旋矩阵;

  2. LeetCode 885. 螺旋矩阵 III:在更大的网格中按螺旋顺序遍历,需要处理网格外的无效位置;

  3. LeetCode 498. 对角线遍历:按对角线方向遍历矩阵,边界控制思路类似;

  4. LeetCode 1424. 对角线遍历 II:稀疏矩阵的对角线遍历,需要结合哈希表优化,但边界控制逻辑相通。

这类矩阵遍历问题的核心是明确遍历范围→设计遍历顺序→处理边界情况,吃透本题的边界收缩思路,能轻松应对各类矩阵遍历场景~