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)

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

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

相关推荐
她的男孩几秒前
页面已发布用户仍进不去:Forge Admin 门户入口与系统菜单挂载解耦
java·spring boot·低代码
不像程序员的程序媛4 分钟前
mybatisplus insert dto未指定id时会如何
java·开发语言
零基础12321 分钟前
基于模型的强化学习与不基于模型的强化学习:核心区别与实战选择
python·算法
我命由我1234523 分钟前
5-2.Python 编码进阶 - 字符串进阶(拼接、普通格式化、普通格式化数字精度控制、快速格式化、遍历、常用方法)
java·开发语言·后端·python·学习·java-ee·学习方法
Pniubi8 小时前
HashMap的扩容和树化边界
java·开发语言
Mikko79 小时前
用 Java 复刻了高中时在塞班上玩的「熔岩矿车」:纯 Swing、零依赖、没有一张图片,一个 jar 双击就能玩
java·游戏开发·swing
知了学历孙老师9 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
Java爱好狂.9 小时前
Java初学者如何一周速通SpringBoot!
java·spring boot·后端·spring·高并发·架构师·java面试
秋饼10 小时前
LLM API密钥动态轮换实战:Spring Vault + Spring AI + 多Provider路由
java·ai·技术分享·后端开发
福兮说11 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json