力扣54. 螺旋矩阵(Java解法)

目描述

给你一个 mn 列的矩阵 matrix,请按照 顺时针螺旋顺序,返回矩阵中的所有元素。

示例 1:

复制代码
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

示例 2:

复制代码
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

提示:

  • m == matrix.length

  • n == matrix[i].length

  • 1 <= m, n <= 10

  • -100 <= matrix[i][j] <= 100

解题思路

螺旋矩阵是一类经典的矩阵遍历问题,核心是模拟顺时针遍历过程 。我们需要按照"向右→向下→向左→向上"的顺序遍历矩阵,同时处理好边界条件,避免重复访问-3-7

下面介绍三种解法,从直观到最优。

解法一:按层模拟(边界收缩法)【最直观】

算法思想

将矩阵看作由若干层组成,每一层是一个矩形环。我们按照顺时针方向遍历每一层,然后收缩边界,进入内层继续遍历,直到所有元素都被访问-6-10

定义四个边界:

  • top:上边界,初始为0

  • bottom:下边界,初始为m-1

  • left:左边界,初始为0

  • right:右边界,初始为n-1

每遍历完一层,就收缩边界:

  • 遍历完上边,top++

  • 遍历完右边,right--

  • 遍历完下边,bottom--

  • 遍历完左边,left++

代码实现

java 复制代码
class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> result = new ArrayList<>();
        if (matrix == null || matrix.length == 0) {
            return result;
        }
        
        int top = 0;
        int bottom = matrix.length - 1;
        int left = 0;
        int right = matrix[0].length - 1;
        
        while (top <= bottom && left <= right) {
            // 1. 从左到右遍历上边
            for (int i = left; i <= right; i++) {
                result.add(matrix[top][i]);
            }
            top++; // 上边界收缩
            
            // 2. 从上到下遍历右边
            for (int i = top; i <= bottom; i++) {
                result.add(matrix[i][right]);
            }
            right--; // 右边界收缩
            
            // 3. 从右到左遍历下边(需要检查上下边界是否交错)
            if (top <= bottom) {
                for (int i = right; i >= left; i--) {
                    result.add(matrix[bottom][i]);
                }
                bottom--; // 下边界收缩
            }
            
            // 4. 从下到上遍历左边(需要检查左右边界是否交错)
            if (left <= right) {
                for (int i = bottom; i >= top; i--) {
                    result.add(matrix[i][left]);
                }
                left++; // 左边界收缩
            }
        }
        
        return result;
    }
}

图解示例

matrix = [[1,2,3],[4,5,6],[7,8,9]] 为例:

步骤 操作 边界状态 遍历结果
初始 - top=0, bottom=2, left=0, right=2 []
第1圈-上边 从左到右遍历第0行 top=1, bottom=2, left=0, right=2 [1,2,3]
第1圈-右边 从上到下遍历第2列 top=1, bottom=2, left=0, right=1 [1,2,3,6,9]
第1圈-下边 从右到左遍历第2行 top=1, bottom=1, left=0, right=1 [1,2,3,6,9,8,7]
第1圈-左边 从下到上遍历第0列 top=2, bottom=1, left=1, right=1 [1,2,3,6,9,8,7,4]
第2圈 top>bottom,结束 - 最终结果:[1,2,3,6,9,8,7,4,5]

复杂度分析

  • 时间复杂度: O(m×n),每个元素恰好访问一次

  • 空间复杂度: O(1),只使用了常数个边界变量

解法二:方向模拟法(偏移量数组)【推荐】

算法思想

使用方向数组来控制遍历方向,同时用一个访问标记数组记录哪些位置已经被访问过-5-9

定义四个方向:

  • 向右:(0, 1)

  • 向下:(1, 0)

  • 向左:(0, -1)

  • 向上:(-1, 0)

(0,0) 开始,按照当前方向移动,如果下一个位置越界或已经访问过,就顺时针旋转90度(改变方向),然后继续移动-2

代码实现

java 复制代码
class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> result = new ArrayList<>();
        if (matrix == null || matrix.length == 0) {
            return result;
        }
        
        int m = matrix.length;      // 行数
        int n = matrix[0].length;   // 列数
        boolean[][] visited = new boolean[m][n]; // 标记是否访问过
        
        // 方向数组:右、下、左、上
        int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        int directionIndex = 0; // 当前方向索引
        
        int row = 0, col = 0; // 当前位置
        
        for (int i = 0; i < m * n; i++) {
            result.add(matrix[row][col]);
            visited[row][col] = true;
            
            // 计算下一个位置
            int nextRow = row + directions[directionIndex][0];
            int nextCol = col + directions[directionIndex][1];
            
            // 如果下一个位置越界或已访问,需要转向
            if (nextRow < 0 || nextRow >= m || nextCol < 0 || nextCol >= n 
                || visited[nextRow][nextCol]) {
                directionIndex = (directionIndex + 1) % 4; // 顺时针旋转方向
                nextRow = row + directions[directionIndex][0];
                nextCol = col + directions[directionIndex][1];
            }
            
            row = nextRow;
            col = nextCol;
        }
        
        return result;
    }
}

图解示例

matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 为例:

步数 当前位置 当前方向 操作 结果集
1 (0,0) 添加1 [1]
2 (0,1) 添加2 [1,2]
3 (0,2) 添加3 [1,2,3]
4 (0,3) 添加4,下一个(0,4)越界,转向下 [1,2,3,4]
5 (1,3) 添加8 [1,2,3,4,8]
6 (2,3) 添加12,下一个(3,3)越界,转向左 [1,2,3,4,8,12]
7 (2,2) 添加11 [1,2,3,4,8,12,11]
8 (2,1) 添加10 [1,2,3,4,8,12,11,10]
9 (2,0) 添加9,下一个(2,-1)越界,转向上 [1,2,3,4,8,12,11,10,9]
10 (1,0) 添加5 [1,2,3,4,8,12,11,10,9,5]
11 (1,1) 添加6,下一个(0,1)已访问,转向右 [1,2,3,4,8,12,11,10,9,5,6]
12 (1,2) 添加7 [1,2,3,4,8,12,11,10,9,5,6,7]

复杂度分析

  • 时间复杂度: O(m×n),每个元素恰好访问一次

  • 空间复杂度: O(m×n),需要 visited 数组记录访问状态

解法三:逐层遍历(代码随想录版)

算法思想

这种方法与解法一类似,但采用了更规范的"左闭右开"区间处理方式。通过计算循环圈数,逐层处理-1-7

代码实现

java 复制代码
class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> result = new ArrayList<>();
        int m = matrix.length;        // 行数
        int n = matrix[0].length;     // 列数
        int start = 0;                 // 每一圈的起始位置
        int loop = Math.min(m, n) / 2; // 循环的圈数
        int mid = loop;                // 中间位置
        int offset = 1;                 // 控制每一条边遍历的长度
        int i, j;
        
        while (loop-- > 0) {
            i = start;
            j = start;
            
            // 从左到右遍历上边
            for (; j < n - offset; j++) {
                result.add(matrix[i][j]);
            }
            
            // 从上到下遍历右边
            for (; i < m - offset; i++) {
                result.add(matrix[i][j]);
            }
            
            // 从右到左遍历下边
            for (; j > start; j--) {
                result.add(matrix[i][j]);
            }
            
            // 从下到上遍历左边
            for (; i > start; i--) {
                result.add(matrix[i][j]);
            }
            
            start++;
            offset++;
        }
        
        // 如果行和列的最小值是奇数,需要处理中间行或中间列
        if (Math.min(m, n) % 2 != 0) {
            if (m > n) { // 行数大于列数,剩下中间列
                for (int k = mid; k < mid + m - n + 1; k++) {
                    result.add(matrix[k][mid]);
                }
            } else { // 列数大于等于行数,剩下中间行
                for (int k = mid; k < mid + n - m + 1; k++) {
                    result.add(matrix[mid][k]);
                }
            }
        }
        
        return result;
    }
}

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
边界收缩法 O(m×n) O(1) 思路直观,无需额外空间 需要处理边界交错情况
方向模拟法 O(m×n) O(m×n) 逻辑清晰,易于理解 需要visited数组,空间较大
逐层遍历法 O(m×n) O(1) 代码规范,左闭右开原则 实现稍复杂,需处理中间行列

代码要点详解

1. 边界收缩法中的条件判断

java 复制代码
// 遍历下边前需要判断top <= bottom
if (top <= bottom) {
    for (int i = right; i >= left; i--) {
        result.add(matrix[bottom][i]);
    }
    bottom--;
}

// 遍历左边前需要判断left <= right
if (left <= right) {
    for (int i = bottom; i >= top; i--) {
        result.add(matrix[i][left]);
    }
    left++;
}

这两个判断很重要,防止在只剩一行或一列时重复遍历-6

2. 方向模拟法中的转向逻辑

java 复制代码
// 计算下一个位置
int nextRow = row + directions[directionIndex][0];
int nextCol = col + directions[directionIndex][1];

// 如果下一个位置越界或已访问,需要转向
if (nextRow < 0 || nextRow >= m || nextCol < 0 || nextCol >= n 
    || visited[nextRow][nextCol]) {
    directionIndex = (directionIndex + 1) % 4; // 顺时针旋转方向
    nextRow = row + directions[directionIndex][0];
    nextCol = col + directions[directionIndex][1];
}

方向数组 {0,1} 表示向右,{1,0} 表示向下,{0,-1} 表示向左,{-1,0} 表示向上。(directionIndex + 1) % 4 实现了顺时针转向-2-5

常见错误与注意事项

  1. 忘记处理空矩阵 :输入可能为 null 或长度为0,需要提前返回空列表

  2. 边界条件错误:在边界收缩法中,遍历下边和左边前必须检查边界是否有效

  3. 重复访问:方向模拟法中必须用 visited 数组记录访问状态

  4. 索引越界:计算下一个位置时,必须先检查是否越界

  5. 单行或单列的特殊处理:当矩阵只有一行或一列时,要避免重复遍历

面试建议

  • 优先写出边界收缩法:这是最直观的解法,代码简洁,无需额外空间

  • 可以提及方向模拟法:作为对比,展示你知道用方向数组模拟遍历的思路

  • 注意边界条件处理:面试官通常会关注你对边界情况的考虑

  • 画图演示:面试时可以用简单例子画图,帮助解释算法过程

相关题目推荐

  • 力扣 59. 螺旋矩阵 II(逆过程:生成螺旋矩阵)

  • 力扣 54. 螺旋矩阵(本题)

  • 力扣 885. 螺旋矩阵 III

  • 力扣 2326. 螺旋矩阵 IV

  • 剑指 Offer 29. 顺时针打印矩阵(与本题完全相同)


以上就是力扣54题"螺旋矩阵"的Java解法详细解析,重点掌握边界收缩法方向模拟法这两种经典解法。如果觉得文章不错,欢迎点赞、收藏、关注三连支持!

相关推荐
njsgcs2 小时前
c# solidworks 获得视图的投影矩阵
矩阵·c#
飞Link3 小时前
别再被异常数据骗了:深度解析 TSTD 异常检测中的重构模型(AutoEncoder 实战)
人工智能·算法·重构·回归
小曹要微笑3 小时前
C#中的各种数据类型
算法·c#·数据类型·c#数据类型
靠沿3 小时前
【优选算法】专题九——链表
数据结构·算法·链表
weixin_649555673 小时前
C语言程序设计第四版(何钦铭、颜晖)第七章利用数组判断上三角矩阵
算法
星爷AG I3 小时前
14-4 运动控制理论:协同理论(AGI基础理论)
算法·机器学习·agi
I_LPL3 小时前
day48 代码随想录算法训练营 图论专题1
java·算法·深度优先·图论·广度优先·求职面试
absunique4 小时前
多路归并算法在外部排序中的实现与优化的技术7
算法
鹿鸣悠悠4 小时前
【AI-08】Prompt(提示词)
人工智能·算法