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)

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

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

相关推荐
重生之小比特1 小时前
【Java SE】数据类型与变量
java·开发语言·python
苏渡苇1 小时前
Spring Insight 里如何对 Span 进行清洗
java·spring boot·后端·spring·系统监控
Yeniden1 小时前
Java 后端从零到企业级:第1篇 Java 基础语法——变量、数据类型与运算符
java·开发语言·apache
邪修king1 小时前
Linux系统篇(二十三) 基础 IO:从“文件”到“文件描述符”,彻底理解重定向
android·java·linux
云雀衔光1 小时前
MCP + 应用生成:让 AI 直接产出可交互的应用
java·人工智能·测试工具·microsoft·交互·ai编程
z_mazin1 小时前
逆向一种私有二进制序列化格式:从零到字节级解析器
网络·python·网络协议·算法·rpc
luj_17681 小时前
罚球线右移破防新策略
c语言·开发语言·网络·经验分享·算法
vivo互联网技术1 小时前
SmartPhotoCrafter: 先思考后修图,统一理解-生成的图像优化新范式
人工智能·算法·计算机视觉
Nuanyt2 小时前
JVM常见核心知识梳理02 类加载 字节码技术 双亲委派 Java内存模型 JMM 并发底层 volatile synchronized 常见排障与调优工具
java·开发语言·jvm