LeetCode 54中等矩阵 · 边界收缩
螺旋矩阵 图解题解
这道题到底在问什么
按顺时针螺旋顺序返回矩阵中所有元素:从左上角开始,向右走到头、再向下、再向左、再向上,一圈圈往里收。
- 输入
- [[1,2,3,4],[12,13,14,5],[11,16,15,6],[10,9,8,7]]
- 输出
- [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
最优解:一步一步想明白
- 3核心一句话:用四个边界框住「未走区域」,走完一条边就收一格——这是不重不漏的关键。
- 4① 向右:沿上边界第 0 行,走到 (0,0)=1,收进序列。
- 5① 向右:沿上边界第 0 行,走到 (0,1)=2,收进序列。
- 6① 向右:沿上边界第 0 行,走到 (0,2)=3,收进序列。
- 7① 向右:沿上边界第 0 行,走到 (0,3)=4,收进序列。
- 8上边界第 0 行整行走完 → 收缩:top 从 0 往下挪到 1,这一行以后不再碰。
- 9② 向下:沿右边界第 3 列,走到 (1,3)=5,收进序列。
- 10② 向下:沿右边界第 3 列,走到 (2,3)=6,收进序列。
- 11② 向下:沿右边界第 3 列,走到 (3,3)=7,收进序列。
- 12右边界第 3 列走完 → 收缩:right 从 3 往左挪到 2。
- 13③ 向左:沿下边界第 3 行,走到 (3,2)=8,收进序列。
- 14③ 向左:沿下边界第 3 行,走到 (3,1)=9,收进序列。
- 15③ 向左:沿下边界第 3 行,走到 (3,0)=10,收进序列。
- 16下边界第 3 行走完 → 收缩:bottom 从 3 往上挪到 2。
- 17④ 向上:沿左边界第 0 列,走到 (2,0)=11,收进序列。
- 18④ 向上:沿左边界第 0 列,走到 (1,0)=12,收进序列。
- 19左边界第 0 列走完 → 收缩:left 从 0 往右挪到 1。一圈走完,进入更内层。
- 20① 向右:沿上边界第 1 行,走到 (1,1)=13,收进序列。
- 21① 向右:沿上边界第 1 行,走到 (1,2)=14,收进序列。
- 22上边界第 1 行整行走完 → 收缩:top 从 1 往下挪到 2,这一行以后不再碰。
- 23② 向下:沿右边界第 2 列,走到 (2,2)=15,收进序列。
- 24右边界第 2 列走完 → 收缩:right 从 2 往左挪到 1。
- 25③ 向左:沿下边界第 2 行,走到 (2,1)=16,收进序列。
- 26下边界第 2 行走完 → 收缩:bottom 从 2 往上挪到 1。
- 27top>bottom 或 left>right,活动区域被收成空,16 个格子全部收集完毕。螺旋序列:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]。
⚠️ 容易写错的地方
✗ 错:向左/向上前不判 top<=bottom / left<=right
✓ 对:走第③④条边前各加一次判断
矩阵只剩单行或单列时,上边界已被走过,再向左/向上会把这行/列重复收一遍
✗ 错:边界收缩时机弄错(走之前就收)
✓ 对:先走完整条边,再收对应边界
提前收缩会漏掉边上的格子
✗ 错:while 条件只判一个边界
✓ 对:top<=bottom 且 left<=right 同时成立才继续
长条矩阵里某个方向先收空,少判一个会越界
完整代码(Python / C++ / Java)
Python
def spiralOrder(matrix):
res = []
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
while top <= bottom and left <= right:
for j in range(left, right + 1): # 右
res.append(matrix[top][j])
top += 1
for i in range(top, bottom + 1): # 下
res.append(matrix[i][right])
right -= 1
if top <= bottom:
for j in range(right, left - 1, -1): # 左
res.append(matrix[bottom][j])
bottom -= 1
if left <= right:
for i in range(bottom, top - 1, -1): # 上
res.append(matrix[i][left])
left += 1
return resC++
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
int top = 0, bottom = matrix.size() - 1;
int left = 0, right = matrix[0].size() - 1;
while (top <= bottom && left <= right) {
for (int j = left; j <= right; j++) // 右
res.push_back(matrix[top][j]);
top++;
for (int i = top; i <= bottom; i++) // 下
res.push_back(matrix[i][right]);
right--;
if (top <= bottom)
for (int j = right; j >= left; j--) // 左
res.push_back(matrix[bottom][j]);
bottom--;
if (left <= right)
for (int i = bottom; i >= top; i--) // 上
res.push_back(matrix[i][left]);
left++;
}
return res;
}
};Java
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> res = new ArrayList<>();
int top = 0, bottom = matrix.length - 1;
int left = 0, right = matrix[0].length - 1;
while (top <= bottom && left <= right) {
for (int j = left; j <= right; j++) // 右
res.add(matrix[top][j]);
top++;
for (int i = top; i <= bottom; i++) // 下
res.add(matrix[i][right]);
right--;
if (top <= bottom)
for (int j = right; j >= left; j--) // 左
res.add(matrix[bottom][j]);
bottom--;
if (left <= right)
for (int i = bottom; i >= top; i--) // 上
res.add(matrix[i][left]);
left++;
}
return res;
}
}复杂度
时间
O(m·n)
每个格子恰好被访问一次,进序列一次
空间
O(1)
除返回的结果数组外,只用四个边界变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 螺旋矩阵 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
除了边界收缩法,还有别的写法吗?+
有。一种是「按层模拟」,外层循环每次处理最外一圈,用 offset 控制层数;另一种是「方向数组 + visited 标记」,按右下左上四个方向走,撞墙或撞到走过的格子就转向。边界收缩法不需要额外 visited 数组,空间 O(1),最常用。
如果要螺旋「生成」一个 n×n 矩阵(LC59)填 1..n²,思路一样吗?+
一样的边界框架,只是把「读 matrix[i][j] 收进序列」换成「matrix[i][j] = cnt++ 填值」,四条边和边界收缩逻辑完全复用。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 螺旋矩阵 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。