LeetCode 54:螺旋矩阵——用四个边界模拟矩阵收缩

LeetCode 54:螺旋矩阵------用四个边界模拟矩阵收缩

一、题目描述

今天做的是 LeetCode 54:螺旋矩阵。

题目要求按照顺时针螺旋顺序,遍历矩阵中的所有元素。

例如:

text 复制代码
1 2 3
4 5 6
7 8 9

按照顺时针方向遍历:

text 复制代码
1 → 2 → 3
        ↓
4 → 5 → 6
↑       ↓
7 ← 8 ← 9

最终结果:

text 复制代码
[1,2,3,6,9,8,7,4,5]

这道题如果直接按照"向右、向下、向左、向上"不断移动坐标来做,比较容易把边界条件搞复杂。

所以我的思路是:

不去记录每一个位置的移动方向,而是直接维护当前还没有遍历的矩形区域。


二、我的思路:维护四个边界

我定义四个变量:

java 复制代码
int top = 0;
int bottom = matrix.length - 1;
int left = 0;
int right = matrix[0].length - 1;

它们分别表示当前还没有遍历区域的:

text 复制代码
top    → 上边界
bottom → 下边界
left   → 左边界
right  → 右边界

例如:

text 复制代码
┌───────────────┐
│ top           │
│               │
│               │
│               │
│ bottom        │
└───────────────┘
 ↑             ↑
left          right

每完成一条边的遍历,就把对应的边界向里面缩一格。

所以整个过程实际上就是:

text 复制代码
遍历上边
    ↓
top++

遍历右边
    ↓
right--

遍历下边
    ↓
bottom--

遍历左边
    ↓
left++

不断缩小范围,直到:

java 复制代码
top > bottom

或者:

java 复制代码
left > right

三、按照"右 → 下 → 左 → 上"依次遍历

代码:

java 复制代码
while (top <= bottom && left <= right) {

    // 从左到右遍历上边
    for (int i = left; i <= right; i++) {
        result.add(matrix[top][i]);
    }
    top++;

    // 从上到下遍历右边
    for (int i = top; i <= bottom; i++) {
        result.add(matrix[i][right]);
    }
    right--;

    // 从右到左遍历下边
    for (int i = right; i >= left && top <= bottom; i--) {
        result.add(matrix[bottom][i]);
    }
    bottom--;

    // 从下到上遍历左边
    for (int i = bottom; i >= top && left <= right; i--) {
        result.add(matrix[i][left]);
    }
    left++;
}

整个循环其实非常有规律:

text 复制代码
① 左 → 右
② 上 → 下
③ 右 → 左
④ 下 → 上

然后重复。

每一轮都会把外面的一圈处理掉。


四、为什么遍历完一条边就要修改边界?

比如第一步:

java 复制代码
for (int i = left; i <= right; i++) {
    result.add(matrix[top][i]);
}
top++;

我们已经把最上面这一行遍历完成:

text 复制代码
1 2 3
4 5 6
7 8 9

此时:

text 复制代码
1 2 3
------
4 5 6
7 8 9

第一行已经处理过了,所以:

java 复制代码
top++;

让 top 从:

text 复制代码
0 → 1

这样下一轮就不会再次访问这一行。

其他三个边界也是一样:

text 复制代码
上边遍历完 → top++

右边遍历完 → right--

下边遍历完 → bottom--

左边遍历完 → left++

可以把它理解成:

每遍历完一条边,就把这条边从"未处理区域"中删除。


五、为什么后面两个 for 要额外判断边界?

这是这道题最容易出问题的地方。

看一个只有一行的矩阵:

text 复制代码
1 2 3 4

第一步:

java 复制代码
for (int i = left; i <= right; i++) {
    result.add(matrix[top][i]);
}

得到:

text 复制代码
1 2 3 4

然后:

java 复制代码
top++;

此时:

text 复制代码
top = 1
bottom = 0

已经满足:

text 复制代码
top > bottom

说明矩阵已经遍历完成。

但是如果没有判断,下面的代码仍然可能继续访问:

java 复制代码
matrix[bottom][i]

这样就会把已经访问过的数据再次加入结果。

所以我的代码:

java 复制代码
for (int i = right; i >= left && top <= bottom; i--) {
    result.add(matrix[bottom][i]);
}

这里:

java 复制代码
top <= bottom

就是在确认:

下面这一行是否真的还存在。

同样,遍历左边的时候:

java 复制代码
for (int i = bottom; i >= top && left <= right; i--) {
    result.add(matrix[i][left]);
}

这里:

java 复制代码
left <= right

是在确认:

左边这一列是否还没有被处理。

这两个判断非常重要,因为矩阵不一定是正方形。

可能是:

text 复制代码
1 × n

也可能是:

text 复制代码
m × 1

还可能在最后一圈只剩下一行或者一列。


六、用 3 × 3 矩阵完整走一遍

例如:

text 复制代码
1 2 3
4 5 6
7 8 9

初始:

text 复制代码
top = 0
bottom = 2
left = 0
right = 2

第一圈

从左到右:

text 复制代码
1 2 3

然后:

text 复制代码
top = 1

从上到下:

text 复制代码
6
9

然后:

text 复制代码
right = 1

从右到左:

text 复制代码
8 7

然后:

text 复制代码
bottom = 1

从下到上:

text 复制代码
4

然后:

text 复制代码
left = 1

此时剩下:

text 复制代码
5

也就是:

text 复制代码
top = 1
bottom = 1
left = 1
right = 1

继续下一轮:

text 复制代码
5

最终:

text 复制代码
[1,2,3,6,9,8,7,4,5]

七、我的最终代码

java 复制代码
class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {

        List<Integer> result = new ArrayList<>();

        int top = 0;
        int bottom = matrix.length - 1;
        int left = 0;
        int right = matrix[0].length - 1;

        while (top <= bottom && left <= right) {

            // 左 → 右
            for (int i = left; i <= right; i++) {
                result.add(matrix[top][i]);
            }
            top++;

            // 上 → 下
            for (int i = top; i <= bottom; i++) {
                result.add(matrix[i][right]);
            }
            right--;

            // 右 → 左
            for (int i = right; i >= left && top <= bottom; i--) {
                result.add(matrix[bottom][i]);
            }
            bottom--;

            // 下 → 上
            for (int i = bottom; i >= top && left <= right; i--) {
                result.add(matrix[i][left]);
            }
            left++;
        }

        return result;
    }
}

八、复杂度分析

虽然代码中有四个 for 循环,但并不是:

text 复制代码
O(n⁴)

或者其他更高的复杂度。

因为每一个矩阵元素最终只会被加入 result 一次。

假设矩阵共有:

text 复制代码
m × n

个元素,那么所有元素最多访问一次。

因此:

text 复制代码
时间复杂度:O(m × n)

结果本身就需要保存所有元素,所以 result 需要:

text 复制代码
O(m × n)

的空间。

如果不计算返回结果本身占用的空间,那么算法额外使用的辅助空间实际上是:

text 复制代码
O(1)

九、这道题的核心

这道题表面上是在考:

如何按照顺时针方向遍历矩阵。

但我觉得真正值得记住的是边界收缩。

不需要真的让一个坐标:

text 复制代码
向右走
↓
向下走
↓
向左走
↓
向上走

而是直接把问题变成:

text 复制代码
当前矩形区域
    ↓
遍历上边
    ↓
缩小 top

遍历右边
    ↓
缩小 right

遍历下边
    ↓
缩小 bottom

遍历左边
    ↓
缩小 left

重复

最终:

text 复制代码
top > bottom
或者
left > right

就说明所有元素都已经处理完成。


十、总结

这道题我采用的是四边界模拟法。

核心就是维护:

java 复制代码
top
bottom
left
right

然后按照:

text 复制代码
左 → 右
上 → 下
右 → 左
下 → 上

的顺序遍历。

每遍历完一条边,就把对应边界向内收缩。

其中最需要注意的是最后两次遍历:

java 复制代码
top <= bottom

和:

java 复制代码
left <= right

它们是为了防止矩阵在最后只剩下一行或一列时,重复遍历已经访问过的元素。

最终复杂度:

text 复制代码
时间复杂度:O(m × n)
额外空间复杂度:O(1)

这道题让我对矩阵遍历有了一个比较实用的思路:

当遍历顺序具有明显的"外圈 → 内圈"特征时,可以考虑用边界不断向内收缩,而不是直接模拟坐标移动。

相关推荐
空奈qwq5 小时前
ANN 全连接神经网络入门指南:从神经元到深度学习的第一步
人工智能·python·深度学习·神经网络·算法·机器学习
hetao17338375 小时前
2026-09-27~28 hetao1733837 的刷题记录
c++·算法
Godspeed Zhao5 小时前
现代智能雷达技术15——算法(1)
人工智能·算法·雷达
代码什么用6 小时前
LeetCode-42. 接雨水
算法·leetcode
lengxuenong6 小时前
枚举及其优化学习手册
c++·学习·算法·青少年编程
小O的算法实验室6 小时前
IEEE TAI,RoCo:基于角色的LLM协作实现自动启发式设计
算法
小范的技术工坊6 小时前
大模型蒸馏
人工智能·算法·数据挖掘·大模型
Benny_Tang6 小时前
P9527 [JOIST 2022] 洒水器 / Sprinkler 题解
c++·算法
程序员清风6 小时前
Java 智能体开发:从对话接口到任务执行
java·人工智能·python