难 · 模拟
螺旋矩阵
像剥洋葱一样处理网格:打印顶行、再打右列、再倒着打底行、再向上打左列 —— 而最后两步之前,各问一句「还有得剥吗」。整个难度就在这一问。
一句话答案维护 <code>top/bottom/left/right</code>;循环:扫顶行、收缩 top;扫右列、收缩 right;<em>若还有行</em> 倒扫底行;<em>若还有列</em> 向上扫左列。
题目
输入: 第一行是 n 和 m(1 ≤ n, m ≤ 10)。接下来 n 行、每行 m 个整数(−100 到 100)。
输出: 从左上角出发、先向右的螺旋遍历顺序,值之间单个空格,输出在一行。
样例:
| 输入 | 输出 |
|---|---|
3 3 | 1 2 3 6 9 8 7 4 5 |
1 4 | 1 2 3 4 |
单行样例放在这里是有意的:扫完顶行后 top 已经越过 bottom,底行扫描必须整个跳过 —— 否则这一行会被打两遍。判题的第二个样例就是陷阱探测器。
你的解答
「运行我的输入」用的自定义输入
判题在浏览器本地运行。死循环会在 3 秒后被自动中断 —— 但还是确保每个循环都有出口。代码随打随存。
卡住了?提示
提示 1 — 一圈 = 四次扫描
顶行从左到右、右列从上到下、底行从右到左、左列从下到上。每次扫完收缩对应边界:top++、right--、bottom--、left++。只要 top <= bottom && left <= right 就继续 —— 区间交叉时洋葱剥完。
提示 2 — 两处复检才是本题
top++ 之后,底行可能已经不存在(单行网格,或螺旋止于一条横带)—— 底行扫描要用 if (top <= bottom) 拦住。对称地,左列扫描用 if (left <= right) 拦住。少了这两处,判题的单行、单列测试就会因为重复访问而挂掉。
提示 3 — 拿非正方形手推一遍
正方形网格把边界 bug 藏得漂漂亮亮 —— 什么都刚好闭合。在纸上手推一遍 2×3 和 3×2,四个循环里的任何错误都会现形。如果你的螺旋在方阵上好好的、上了判题就挂,原因就在这。
题解(先自己试试!)
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
int a[12][12];
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> a[i][j];
int top = 0, bottom = n - 1, left = 0, right = m - 1;
bool first = true;
while (top <= bottom && left <= right) {
for (int j = left; j <= right; j++) {
if (!first) cout << " ";
cout << a[top][j]; first = false;
}
top++;
for (int i = top; i <= bottom; i++) {
if (!first) cout << " ";
cout << a[i][right]; first = false;
}
right--;
if (top <= bottom) {
for (int j = right; j >= left; j--) {
if (!first) cout << " ";
cout << a[bottom][j]; first = false;
}
bottom--;
}
if (left <= right) {
for (int i = bottom; i >= top; i--) {
if (!first) cout << " ";
cout << a[i][left]; first = false;
}
left++;
}
}
cout << endl;
return 0;
}
「边界收缩式模拟」是旋转、遍历、泛洪填色这类网格问题的共同骨架。和你处理网格的另一种方式 —— 矩阵转置 的整行换下标 —— 对照一下,注意区别:转置动的是下标,螺旋动的是*边*。两者之后的下一项网格技能,是向矩形提问:二维区间求和。
深度解析
为什么这样做对
四条边界(top/bottom/left/right)定义未访问的环;每圈打一圈、每次扫描收缩一条边界。收缩后的两处复检之所以存在,是因为环可能只剩一行或一列薄薄一层 —— 底行打完再打左列,同样的格子会走两遍。
代价
O(n·m):每个格子恰好打印一次,每次扫描的边界算术是常数。网格之外空间 O(1)。
常见错法
- 底行扫描前少了
if (top <= bottom)复检(或左列少了复检):单行、单列网格会被打两遍 —— 判题的非方形测试就是为这个准备的。 - 底行从左到右扫、左列从上到下扫:螺旋方向是交替的,每圈后半程必须倒序循环(
j--、i--)。 - 只信方形测试:3×3 恰好闭合,所有 bug 都被藏住。只有手推 2×3 和 3×2 才能暴露四个循环边界。
记住这两句
- 一圈 = 四次扫描;每次扫完收缩对应边界。
- 两处复检(还有行吗?还有列吗?)是非方形测试买来的。
🐞 这段是错的 —— 点开,亲手让它挂一次
底行、左列扫描前没复检 —— 单行会被打两遍
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
int a[12][12];
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> a[i][j];
int top = 0, bottom = n - 1, left = 0, right = m - 1;
bool first = true;
while (top <= bottom && left <= right) {
for (int j = left; j <= right; j++) { if (!first) cout << " "; cout << a[top][j]; first = false; }
top++;
for (int i = top; i <= bottom; i++) { if (!first) cout << " "; cout << a[i][right]; first = false; }
right--;
for (int j = right; j >= left; j--) { if (!first) cout << " "; cout << a[bottom][j]; first = false; } // bug: no guard
bottom--;
for (int i = bottom; i >= top; i--) { if (!first) cout << " "; cout << a[i][left]; first = false; } // bug: no guard
left++;
}
cout << endl;
return 0;
}