力扣hot100-54.螺旋矩阵-模拟边界控制详解

LeetCode 54. 螺旋矩阵:模拟 + 边界控制详解

1. 这道题属于什么算法思想

这题主要归类为:

text 复制代码
矩阵 / 模拟 / 边界控制

它不需要动态规划、贪心、回溯或图论。

题目的关键是:

text 复制代码
每次按顺时针走完当前矩阵最外面的一圈,
再把未遍历区域的四条边界向内收缩。

因此需要维护:

text 复制代码
top:当前未遍历区域的上边界。
bottom:当前未遍历区域的下边界。
left:当前未遍历区域的左边界。
right:当前未遍历区域的右边界。

2. 这道题到底在问什么

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

例如:

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

顺时针螺旋顺序是:

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

最终返回:

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

不要把它理解成"在矩阵里转弯走路"。更稳定的理解是:

text 复制代码
每次取走当前最外面一圈。

对于 3 x 3 矩阵:

text 复制代码
第一圈:
1  2  3
4     6
7  8  9

第二圈:
5

3. 四条边界如何表示未遍历区域

对于 m 行、n 列矩阵,初始边界是:

text 复制代码
top = 0
bottom = m - 1
left = 0
right = n - 1

例如 3 x 3 矩阵:

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

表示当前未遍历区域是:

text 复制代码
行范围:0 到 2
列范围:0 到 2

每走完一条边,就说明那条边已经完全处理,不应该再访问,因此边界要向内收缩。

4. 每一圈固定按上、右、下、左走

当前区域由:

text 复制代码
top、bottom、left、right

确定。

一圈的顺时针顺序固定为:

text 复制代码
1. 上边:从左到右。
2. 右边:从上到下。
3. 下边:从右到左。
4. 左边:从下到上。

4.1 从左到右走上边

访问:

text 复制代码
matrix[top][left..right]

走完后:

text 复制代码
top++

因为原来的最上面一行已经处理完。

4.2 从上到下走右边

访问:

text 复制代码
matrix[top..bottom][right]

注意此时 top 已经加一,因此不会重复访问右上角。

走完后:

text 复制代码
right--

4.3 从右到左走下边

访问:

text 复制代码
matrix[bottom][right..left]

走完后:

text 复制代码
bottom--

4.4 从下到上走左边

访问:

text 复制代码
matrix[bottom..top][left]

走完后:

text 复制代码
left++

这样一圈结束,未遍历矩形区域缩小一层。

5. 用 3 x 3 矩阵完整推导

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

初始:

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

第一圈:走上边

访问:

text 复制代码
matrix[0][0..2] = 1, 2, 3

结果:

text 复制代码
result = [1, 2, 3]

上边处理完成:

text 复制代码
top = 1

第一圈:走右边

访问:

text 复制代码
matrix[1..2][2] = 6, 9

结果:

text 复制代码
result = [1, 2, 3, 6, 9]

右边处理完成:

text 复制代码
right = 1

第一圈:走下边

访问:

text 复制代码
matrix[2][1..0] = 8, 7

结果:

text 复制代码
result = [1, 2, 3, 6, 9, 8, 7]

下边处理完成:

text 复制代码
bottom = 1

第一圈:走左边

访问:

text 复制代码
matrix[1..1][0] = 4

结果:

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

左边处理完成:

text 复制代码
left = 1

第一圈结束后:

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

只剩:

text 复制代码
matrix[1][1] = 5

继续下一圈后,最终结果为:

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

6. 为什么下边和左边需要额外判断

最容易出错的是走完上边和右边后,继续走下边、左边时可能发生重复访问。

单行矩阵

text 复制代码
matrix = [[1, 2, 3]]

第一步从左到右已经访问:

text 复制代码
1, 2, 3

随后:

text 复制代码
top = 1
bottom = 0

说明已经没有剩余行。

如果仍然执行"从右到左走下边",会把同一行重复加入结果。

因此走下边前必须判断:

java 复制代码
if (top <= bottom)

单列矩阵

text 复制代码
matrix =
[
  [1],
  [2],
  [3]
]

第二步从上到下已经访问:

text 复制代码
2, 3

之后:

text 复制代码
left = 0
right = -1

说明已经没有剩余列。

如果仍然执行"从下到上走左边",会把同一列重复加入结果。

因此走左边前必须判断:

java 复制代码
if (left <= right)

7. Java 代码完整注释

java 复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        // 保存螺旋遍历结果。
        List<Integer> result = new ArrayList<>();

        int m = matrix.length;
        int n = matrix[0].length;

        // 当前未遍历区域的四条边界。
        int top = 0;
        int bottom = m - 1;
        int left = 0;
        int right = n - 1;

        // 只要未遍历区域仍是一个有效矩形,就继续处理下一圈。
        while (top <= bottom && left <= right) {
            // 1. 从左到右遍历当前区域的上边。
            for (int j = left; j <= right; j++) {
                result.add(matrix[top][j]);
            }

            // 上边处理完成,向下收缩。
            top++;

            // 2. 从上到下遍历当前区域的右边。
            for (int i = top; i <= bottom; i++) {
                result.add(matrix[i][right]);
            }

            // 右边处理完成,向左收缩。
            right--;

            // 3. 从右到左遍历当前区域的下边。
            // 单行矩阵时,上边已经处理过这一行,不能重复访问。
            if (top <= bottom) {
                for (int j = right; j >= left; j--) {
                    result.add(matrix[bottom][j]);
                }

                // 下边处理完成,向上收缩。
                bottom--;
            }

            // 4. 从下到上遍历当前区域的左边。
            // 单列矩阵时,右边已经处理过这一列,不能重复访问。
            if (left <= right) {
                for (int i = bottom; i >= top; i--) {
                    result.add(matrix[i][left]);
                }

                // 左边处理完成,向右收缩。
                left++;
            }
        }

        return result;
    }
}

8. 用长方形矩阵验证

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

第一圈:

text 复制代码
上边:1, 2, 3, 4
右边:8, 12
下边:11, 10, 9
左边:5

得到:

text 复制代码
[1, 2, 3, 4, 8, 12, 11, 10, 9, 5]

剩余中间区域:

text 复制代码
[6, 7]

最终结果:

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

9. 复杂度分析

矩阵中的每个元素恰好加入 result 一次。

时间复杂度:

text 复制代码
O(m * n)

除了返回结果 result 外,只维护了四个边界变量。

额外空间复杂度:

text 复制代码
O(1)

如果把题目要求返回的 result 也算入空间,则结果本身占用:

text 复制代码
O(m * n)

10. 总结

这题属于:

text 复制代码
矩阵 / 模拟 / 边界控制

每次循环按固定顺序走完当前最外圈:

text 复制代码
上 -> 右 -> 下 -> 左

每走完一条边,就将对应边界向内收缩:

text 复制代码
上边结束:top++
右边结束:right--
下边结束:bottom--
左边结束:left++

最容易遗漏的是:

text 复制代码
走下边前检查 top <= bottom。
走左边前检查 left <= right。

它们用于防止单行、单列或最后只剩一行/一列时重复访问元素。

可以把整题记成一句话:

text 复制代码
按上、右、下、左取走一圈,再向内收缩四条边界。
相关推荐
变量未定义~1 小时前
虚拟节点-星石传送阵(4星)、强连通分量
数据结构·算法
IT方大同1 小时前
C语言分支与循环语句
c语言·开发语言·算法
待磨的钝刨2 小时前
深入理解主成分分析(PCA)
人工智能·线性代数·算法·机器学习
geovindu3 小时前
java: Backtracking Algorithm
java·开发语言·windows·后端·算法·回溯算法
晚风叙码3 小时前
C++ vector底层模拟实现与迭代器失效深度剖析
c++·算法
To_OC11 小时前
LC 51 N 皇后:我以为难的是回溯,结果栽在了对角线下标
javascript·算法·leetcode
2401_8414956412 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
爱刷碗的苏泓舒16 小时前
PPP-AR 中的参考星选取:数学原理、评价指标与切换处理
算法·gnss·模糊度固定·ppp-ar·星间单差·参考星·卫星端偏差