01 / 本课学习路线
本课学习路线
阅读与推演约 108 分钟,练习约 62 分钟
02 / 学习目标与先修自测
学完本课你能做什么,以及开始前需要会什么
读题时定下 m 行 n 列,访问前先判边界,填充题先算内容再处理排版。
| 学完后能做的事 | 正文位置 | 检查方式 |
|---|---|---|
| 读题时把「长宽 / 宽高 / 行列」正确对应到 m 行 n 列,并用非方阵样例验证 | 第 03、08 节 | 自查第 2、6 条 |
| 写出方向数组与边界函数,访问前先判边界,知道 Python 负下标的行为 | 第 04 节 | 自查第 3、5 条、练习 4 |
| 逐格判定「自身或四邻有车」,通过(AC)P2503 | 第 05、07 节 | 必做任务 1 |
| 手算螺旋填充的四段顺序与边界收缩,通过 P2529 | 第 06、07 节 | 自查第 4 条、必做任务 2 |
| 把 (i, j) → (j, n − 1 − i) 这类坐标映射用在旋转题上 | 第 04 节末的补充学习 | 练习 5 |
先修自测:下面 5 题请先自己写答案,再展开对照。答不出的按括号里的位置补看再回来。本课假定你会上一课的逐字符扫描与列表操作。
| 题号 | 题目 | 补看位置 |
|---|---|---|
| 自测 1 | grid = [[0] * 3 for _ in range(2)] 建出几行几列?grid[1][2] 是第几行第几列(从 0 数)? | 列表与 嵌套循环 |
| 自测 2 | for r in range(2): for c in range(3): print(r, c) 一共打印几行?第一行和最后一行各是什么? | 嵌套循环 |
| 自测 3 | x = [10, 20, 30],x[-1] 是什么?x[3] 会怎样? | 列表 |
| 自测 4 | 0 <= 2 < 3 成立吗?0 <= -1 < 3 呢? | 布尔与比较 |
| 自测 5 | (n + m - 1) // m 在 n=9、m=4 时等于多少?它和 ⌈9/4⌉ 一样吗? | 数字与运算符(整数除法与取余);向上取整的写法见本课第 03 节术语表末行 |
展开先修自测答案
自测 1:2 行 3 列;grid[1][2] 是第 1 行第 2 列(从 0 数),即最后一行的最后一个格子。第一维是行、第二维是列,本课全程用 grid[r][c]。
自测 2:6 行;第一行 0 0,最后一行 1 2。外层循环走行、内层循环走列,这就是遍历整张网格的写法。
自测 3:x[-1] 是 30(倒数第一个,不报错);x[3] 抛出下标越界异常(IndexError)。负下标不报错正是第 04 节要防的问题——在网格题里它会悄悄取到最后一行。
自测 4:成立;不成立(−1 小于 0)。0 <= r < m and 0 <= c < n 就是边界判定式,两端都要写。
自测 5:(9 + 4 − 1) // 4 = 12 // 4 = 3,与 ⌈9/4⌉ = 3 相同。这是不用浮点数的向上取整写法,P2529 的列数就靠它。
03 / 概念与术语
行列、宽高、0/1 基、方向数组、边界函数
网格题的错误大半来自约定不统一。把每个名字的含义和代码写法先定下来,本课和模块 3 都按同一套用。
| 术语 | 含义 | 代码写法 / 约定 |
|---|---|---|
| m 行 n 列 | m 是行数(竖着数几行),n 是列数(每行几个) | grid = [[0] * n for _ in range(m)];len(grid) == m,len(grid[0]) == n |
| 宽 W、高 H | 宽是列数、高是行数 | 读题先写 m = H、n = W |
| 坐标 (r, c) | 第 r 行第 c 列,从 0 数 | grid[r][c];第一维是行 |
| 0 基 / 1 基 | 代码里下标从 0 起;题目可能从 1 起 | 输入时减一、输出时加一,内部一律 0 基 |
| 方向数组 | 四个相邻方向的行列增量 | DIRS = [(-1, 0), (1, 0), (0, -1), (0, 1)](上、下、左、右) |
| 四邻 | 上下左右四个相邻格子里在界内的那些 | 对每个 (dr, dc) 算 (r + dr, c + dc),先判边界 |
| 边界函数 | 判断 (r, c) 是否在网格内 | def in_bounds(r, c): return 0 <= r < m and 0 <= c < n |
| 边界变量 | 螺旋填充时未填区域的上、下、左、右四条边 | top, bottom, left, right;每填完一段收缩一条 |
| 向上取整 | n 个数分 m 行、每行个数相同时的最少列数 | cols = (n + m - 1) // m |
关于「长宽」:P2503 的题面写「输入 m, n 表示长宽」,随后是 m 行、每行 n 个数——所以 m 是行数、n 是列数,与「m 行 n 列」一致。遇到只写「长宽」不写「行列」的题,按输入格式里「接下来 m 行」这句话定行数,再用非方阵样例(如 2 行 3 列)跑一遍确认没接反。
04 / 边界与遍历
越界检查:固定成一个函数
把「先判边界」写成一个函数,所有访问都经过它——这一个习惯能消除网格题里大半的运行错误(RE)和不报错的答案错误(WA)。
| 位置 | 上 (r−1, c) | 下 (r+1, c) | 左 (r, c−1) | 右 (r, c+1) | 合法邻居数 |
|---|---|---|---|---|---|
| 中心 (1,1) | (0,1) ✓ | (2,1) ✓ | (1,0) ✓ | (1,2) ✓ | 4 |
| 上边 (0,1) | (−1,1) ✗ 行 −1 | (1,1) ✓ | (0,0) ✓ | (0,2) ✓ | 3 |
| 左上角 (0,0) | (−1,0) ✗ | (1,0) ✓ | (0,−1) ✗ 列 −1 | (0,1) ✓ | 2 |
| 右下角 (2,2) | (1,2) ✓ | (3,2) ✗ 行 3 ≥ m | (2,1) ✓ | (2,3) ✗ 列 3 ≥ n | 2 |
判定式 0 <= r < m and 0 <= c < n,四个条件缺一个都会放过一种越界:漏 0 <= r 放过行 −1,漏 r < m 放过行 3。
Python 负下标:不报错的越界
grid = [[0,0,0],[0,0,0],[1,0,0]] # 只有 (2,0) 有车 只判上界(r < m and c < n)、不判下界时: (0,0) 的「上」邻居 (−1,0) 通过判定 → grid[-1][0] 取到最后一行 → 读到 1 → (0,0) 被误判为需要监控 C++ 里 grid[-1][0] 是运行错误;Python 里它合法地取到最后一行,错误从运行错误变成难查的答案错误 结论:下界 0 <= r 和 0 <= c 必须写,而且判定要发生在取值之前
边界安全的二维练习模板(按注释补全)
Python# 练习模板:请补全下面标注为「步骤」的部分,其余可直接保留
import sys
def main() -> None:
data = sys.stdin.read().split()
# 步骤 1:在这里读出 m 行 n 列(先确认题目给出的是「行列」还是「宽高」)
m = n = 0
grid = [[0] * n for _ in range(m)]
DIRS = [(-1, 0), (1, 0), (0, -1), (0, 1)]
def in_bounds(r: int, c: int) -> bool:
return 0 <= r < m and 0 <= c < n
# 步骤 2:在这里写遍历/填充逻辑——访问邻居前必须先调用 in_bounds
# 步骤 3:按题目要求的格式打印矩阵(行内分隔符、行末不多空格)
main()将边界判断封装为 in_bounds 函数,可统一多处访问条件:网格题里它会被调用几十次,逐处展开容易写错其中一处,形成隐蔽错误。补全后的完整程序在第 07 节展开区,练习 4 会用到。
补充学习(选学)行列颠倒为什么不易发现约 4 分钟方阵样例可能不易暴露问题,需用非方阵用例验证
把「3 行 5 列」建成 5 行 3 列,在方阵样例(m=n)上可能不易暴露——转置后的方阵还是同形状;到了非方阵用例才越界或错位。防范方法:读入后立刻打印行数与列数(len(grid)、len(grid[0]))作为调试,或者用非方阵样例先自测,确认 m、n 没接反再往下写。第 08 节的表给了一个接反后答案不同的具体输入。
补充学习(选学)Python 负下标:不报错的越界约 3 分钟grid[-1] 是最后一行——不报错的答案错误的来源
C++ 里 grid[-1][0] 是运行错误,Python 里它合法地取到最后一行——错误被语言特性掩盖,判题结果从运行错误变成更难排查的答案错误。所以用 Python 写网格题,方向数组、边界、访问顺序和输出格式要分别检查,不要依赖越界报错来发现问题。
补充学习(选学)矩阵置零:先记录原始位置,再统一修改约 4 分钟边扫描边置零会让新写入的 0 继续扩散
「把有 0 的行和列全部置零」这类原地修改题,先读后写:第一遍只记录原始 0 所在的行号和列号(放进两个集合),第二遍再统一修改。若边扫描边置零,新写入的 0 会被后续扫描当成原始 0,把本不该置零的行列也改成 0,最终整张矩阵都被改错。基础题 H100051 练的就是这两遍扫描的顺序。
原地修改省下的是整张矩阵的副本:一张 1920×1080 的灰度图有 2,073,600 个格子,新建一份就是两百万次额外写入。省副本的代价是要安排好写入顺序、不覆盖还没读到的值。
补充学习(选学)顺时针旋转的坐标映射约 5 分钟(i, j) → (j, n − 1 − i),可拆成转置加逐行反转
手算:3×3 矩阵顺时针旋转 90°
原矩阵 转置后 逐行反转后(= 顺时针 90°) 1 2 3 1 4 7 7 4 1 4 5 6 → 2 5 8 → 8 5 2 7 8 9 3 6 9 9 6 3 映射: (0,0)→(0,2) (2,1)→(1,0) 一般式 (i, j) → (j, n − 1 − i)
顺时针转 90° 后,第 i 行整体立起来,变成从右往左数第 i 列,也就是第 n − 1 − i 列;元素原来在行内第 j 个位置,转完就落在这一列的第 j 行,合起来就是 (i, j) → (j, n − 1 − i)。转置把 (i, j) 送到 (j, i),逐行反转再把 (j, i) 送到 (j, n − 1 − i),两步接力正好等于目标映射。同一套推理可以组合出其它旋转:先逐行反转再转置是逆时针 90°,顺时针做两遍是 180°。
第一次可以先用新矩阵按映射写入确认方向;通过后再尝试原地做法(沿主对角线转置,然后反转每一行)。边界至少用 1×1、奇数阶(3×3)和偶数阶(2×2、4×4)各验证一次,旋转题只限定方阵。基础题 H100053 练这一条。
补充学习(选学)矩阵存储与下标换算约 6 分钟共享行引用陷阱,以及一维编号(idx)= i×n + j 的互逆换算
建矩阵:独立的行与共享的行
Pythongrid_ok = [[0] * 3 for _ in range(2)]
grid_bad = [[0] * 3] * 2
grid_ok[0][0] = 1 # 只改第一行
grid_bad[0][0] = 1 # 两行一起变
assert grid_ok == [[1, 0, 0], [0, 0, 0]]
assert grid_bad == [[1, 0, 0], [1, 0, 0]][[0] * n for _ in range(m)] 得到 m 行互相独立的列表;[[0] * n] * m 得到的是同一行的 m 个引用——改一格,每一行的同一位置一起变。判题表现是难以定位的答案错误。
m×n 矩阵按行存成一维数组时,(i, j) 的一维编号(idx)是 idx = i×n + j:前面完整走过 i 行、每行 n 个,再加行内偏移 j。反过来 i = idx // n、j = idx % n。用 3×4 矩阵验证:(1, 2) → 1×4 + 2 = 6;6 // 4 = 1,6 % 4 = 2,正好还原。乘的是列数 n,写成行数 m 是这条换算最常见的错误。
基础题 H100056 的二分建立在这条换算上:每行首元素都大于上一行末元素时,整张矩阵可以看成长度 m×n 的升序数组来二分,取到中点下标(mid)后用整除和取模找回行列。非方阵可以用来检查行列、共享引用和展平换算是否写对。
05 / 完整手算例:P2503
逐格判定「自身或四邻有车」
P2503「统计监控」:m 行 n 列的停车场,0 空位、1 有车;一个车位的监控器需要打开,当且仅当它自己或上下左右四个相邻车位里有车。输出需要打开的监控器数量。题面样例在题目页查看;下面用自拟的 3×3 样例逐格推演。
自拟样例:3 3 / 0 0 0 / 0 1 0 / 0 0 0(只有中心 (1,1) 有车)
行 0: (0,0) 四邻 (1,0)(0,1) 都是 0 → 不开 | (0,1) 下邻 (1,1)=1 → 开 | (0,2) 四邻 (1,2)(0,1) 都是 0 → 不开 行 1: (1,0) 右邻 (1,1)=1 → 开 | (1,1) 自己有车 → 开 | (1,2) 左邻 (1,1)=1 → 开 行 2: (2,0) 不开 | (2,1) 上邻 (1,1)=1 → 开 | (2,2) 不开 输出 5(车位自己 + 上下左右四个)
| 输入 | 需要打开的车位 | 输出 |
|---|---|---|
| 3 3 / 1 0 0 / 0 0 0 / 0 0 0 | (0,0) 自己;(1,0)、(0,1) 各有一个邻居是它 | 3 |
| 2 3 / 1 0 1 / 0 0 0 | (0,0)(0,2) 自己;(0,1) 左右都是车;(1,0)(1,2) 上邻是车;(1,1) 四邻都是 0 | 5 |
第二组是 2 行 3 列的非方阵,但把它按 3 行 2 列读也恰好输出 5,分辨不出行列接反;要检验接反,用第 08 节错误表的 2 3 / 1 1 1 / 0 0 0(正确 6、接反 5)。逐格判定时每个格子只数一次——一个格子有两个邻居有车也只开一个监控器。
06 / 完整手算例:P2529
螺旋填充:每一段与四个边界变量的值
P2529「螺旋数字矩阵」:给数字个数 n 和行数 m,从左上角的 1 开始顺时针向内依次填 1…n;每行个数相同、列数尽可能少(cols = ⌈n/m⌉)、优先填外圈、数字不够的格子用单个 * 填充。下面用 n=9、m=4 完整推演。
| 段 | 边界变量 (top, bottom, left, right) | 填入的格子 | 填的数字 | 填完后收缩 |
|---|---|---|---|---|
| 上行 从左到右 | (0, 3, 0, 2) | (0,0)(0,1)(0,2) | 1 2 3 | top → 1 |
| 右列 从上到下 | (1, 3, 0, 2) | (1,2)(2,2)(3,2) | 4 5 6 | right → 1 |
| 下行 从右到左 | (1, 3, 0, 1) | (3,1)(3,0) | 7 8 | bottom → 2 |
| 左列 从下到上 | (1, 2, 0, 1) | (2,0) | 9 | left → 1;num=10 > 9 停止 |
结果:第 0 行 1 2 3,第 1 行 * * 4,第 2 行 9 * 5,第 3 行 8 7 6。三个 * 是数字不够的格子。每段填完才收缩对应的边界;一旦当前数字 num 超过 n 就停止,不再继续填。
n=11,m=3:列数 cols=4,只有一个 *
上行 (0,2,0,3): 1 2 3 4 → top=1 右列 (1,2,0,3): (1,3)=5 (2,3)=6 → right=2 下行 (1,2,0,2): (2,2)=7 (2,1)=8 (2,0)=9 → bottom=1 左列 (1,1,0,2): (1,0)=10 → left=1 上行 (1,1,1,2): (1,1)=11;num=12 > 11 停止 → (1,2) 保持 * 输出: 1 2 3 4 / 10 11 * 5 / 9 8 7 6
停止条件以 num > n(数字用完)为主:列数按 n/m 向上取整,格子数不少于 n,所以数字总会在格子用完之前或恰好同时用完,num > n 单独就能让本程序正常结束。上边界超过下边界、左边界超过右边界这两条区间判断是防御性的,保证四条边各自收缩后不会去访问已经不存在的行列;对本程序的合法输入,去掉它们输出不变。真正会出错的是边界不收缩:n=9、m=3 时 cols=3、正好填满,最后一圈只剩中心一格 (1,1),由「上行」这一段填入 9 后停止;若漏掉 top += 1,第二段「右列」会从第 0 行重新开始,把 (0,2) 的 3 覆盖成 4,第 08 节错误表给出了完整输出。
07 / 从步骤到程序
两道必做题的完整参考程序,每一步落在哪几行
先自己写完并提交一次,再展开对照;对照时用第 05、06 节的手算例逐格核对。
| 步骤 | 代码 | 说明 |
|---|---|---|
| 列数 | cols = (n + m - 1) // m | 向上取整,不用浮点 |
| 全填 * | grid = [["*"] * cols for _ in range(m)] | 每行独立的列表(不能写成 [["*"] * cols] * m) |
| 四段填充 | 四个 for,各自固定一条边、沿另一维走 | 上行、右列、下行、左列的方向各不相同 |
| 收缩 | 每段之后 top += 1 / right -= 1 / bottom -= 1 / left += 1 | 先填后收缩 |
| 停止 | while num <= n and top <= bottom and left <= right,段内 if num > n: break | 两条停止条件 |
| 输出 | " ".join(row) | 行内单个空格分隔,行末无空格 |
展开完整参考程序 1:P2503 统计监控(先自己写完并提交一次,再展开对照)
完整程序:P2503(标准输入 → 标准输出)
Pythonimport sys
data = sys.stdin.read().split()
m, n = int(data[0]), int(data[1]) # m 行 n 列(题面写「长宽」;用样例确认哪个是行数)
grid = [[int(data[2 + i * n + j]) for j in range(n)] for i in range(m)]
DIRS = [(-1, 0), (1, 0), (0, -1), (0, 1)] # 上、下、左、右
def in_bounds(r, c):
return 0 <= r < m and 0 <= c < n
count = 0
for r in range(m):
for c in range(n):
need = grid[r][c] == 1 # 自己有车
if not need:
for dr, dc in DIRS:
nr, nc = r + dr, c + dc
if in_bounds(nr, nc) and grid[nr][nc] == 1: # 先判边界,再取值
need = True
break
if need:
count += 1
print(count)自测用例:第 05 节三组自拟输入(5 / 3 / 5)。in_bounds 在取值之前调用;need 一旦为真就 break,一个格子只计一次。
展开完整参考程序 2:P2529 螺旋数字矩阵
完整程序:P2529(标准输入 → 标准输出)
Pythonimport sys
n, m = map(int, sys.stdin.read().split()) # n 个数字,m 行
cols = (n + m - 1) // m # 列数尽可能少:向上取整 n/m
grid = [["*"] * cols for _ in range(m)] # 先全填 *,数字不够的格子保持 *
top, bottom, left, right = 0, m - 1, 0, cols - 1 # 四个边界
num = 1
while num <= n and top <= bottom and left <= right:
for c in range(left, right + 1): # 上行:从左到右
if num > n: break
grid[top][c] = str(num); num += 1
top += 1
for r in range(top, bottom + 1): # 右列:从上到下
if num > n: break
grid[r][right] = str(num); num += 1
right -= 1
for c in range(right, left - 1, -1): # 下行:从右到左
if num > n or top > bottom: break
grid[bottom][c] = str(num); num += 1
bottom -= 1
for r in range(bottom, top - 1, -1): # 左列:从下到上
if num > n or left > right: break
grid[r][left] = str(num); num += 1
left += 1
for row in grid:
print(" ".join(row))自测用例:9 4、11 3、9 3(输出见第 06、08 节)、5 3(练习 3),再加 3 5(列数 1,输出 1 / 2 / 3 / * / *)和 25 5(正好填满的 5×5)。题目页参考题解用递归收缩子矩阵,本程序用四个边界变量迭代,结果相同。
08 / 边界、反例与复杂度
错误做法在具体输入上各输出什么
下面每一行都是一个具体的错误程序,给出输入、错误输出与正确输出;把参考程序改成对应写法就能复现。
| 错误做法 | 输入 | 错误输出 | 正确输出 | 判题结果 |
|---|---|---|---|---|
| P2503 行列接反(按 n 行 m 列读) | 2 3 / 1 1 1 / 0 0 0 | 读成 3 行 2 列 [[1,1],[1,0],[0,0]] → 5 | 6 | 答案错误(WA) |
| P2503 只判上界、不判下界 | 3 3 / 0 0 0 / 0 0 0 / 1 0 0 | (0,0) 通过 grid[-1][0] 读到最后一行的车 → 4 | 3 | 答案错误(WA) |
| P2503 先取值再判边界 | 3 3 / 0 0 0 / 0 0 0 / 1 0 0 | 扫到右上角 (0,2) 时先取右邻 grid[0][3] → 下标越界异常(IndexError) | 3 | 运行错误(RE) |
P2529 用 [["*"] * cols] * m 建矩阵 | 9 4 | 四行共享一个列表,最后四行相同 | 第 06 节的 4 行 | 答案错误(WA) |
P2529 段内漏 if num > n: break | 9 4 | num 超过 9 后继续写入 10、11… 覆盖 * | 第 06 节的 4 行 | 答案错误(WA) |
P2529 漏掉 top += 1(上边界不收缩) | 9 3 | 1 2 4 / 9 * 5 / 8 7 6:右列从第 0 行重填,(0,2) 被 4 覆盖,(1,1) 永远轮不到 | 1 2 3 / 8 9 4 / 7 6 5 | 答案错误(WA) |
第一行的正确输出 6:2 行 3 列里第 0 行全是车(3 个),第 1 行每个格子的上邻都是车(3 个)。第二行的错误输出 4:正确的 3 个是 (2,0) 自己、(1,0)、(2,1),错误程序多算了 (0,0)。
| 题目 | 输入规模 | 参考程序的时间与空间 | 结论 |
|---|---|---|---|
| P2503 | 1 < m, n ≤ 20 | O(m·n) 时间(每格最多看 4 个邻居);O(m·n) 空间 | 最多 400 格 |
| P2529 | 0 < n, m < 999 | O(m·cols) 时间;O(m·cols) 空间,cols = ⌈n/m⌉ | 最多 1,994 格(n=998、m=997 时);每格写一次 |
本课都是 O(m·n) 遍历,按量级估算远小于时限;若超时,先查是否在每个格子上再做了一次整表扫描这类多余循环。
09 / 渐进练习与参考答案
跟做 → 改一个条件 → 独立实现 → 迁移
每题先在纸上或文件里做完,再展开答案。
练习 1(跟做):3 行 4 列的网格(m=3,n=4),写出 (0,0)、(0,3)、(1,1)、(2,2)、(1,3) 各有几个合法四邻,并列出 (1,3) 的合法邻居坐标。
展开练习 1 答案
(0,0) 角 → 2;(0,3) 角 → 2;(1,1) 内部 → 4;(2,2) 下边 → 3;(1,3) 右边 → 3,合法邻居 (0,3)、(2,3)、(1,2)(右邻 (1,4) 的列 4 ≥ n 出界)。做错最常见的原因:把 n=4 记成 3,让 (1,3) 本身就「出界」。
练习 2(改一个条件):P2503 输入 2 3 / 1 0 1 / 0 0 0 改成 2 3 / 1 0 0 / 0 0 1,输出什么?
展开练习 2 答案
(0,0) 自己;(0,1) 左邻;(1,0) 上邻;(1,2) 自己;(0,2) 下邻;(1,1) 右邻 → 6 个格子全部需要打开,输出 6。对照第 05 节的原输入(5):把右上角的车移到右下角后,(1,1) 多了一个有车的邻居。
练习 3(改一个条件):P2529 输入 5 3,先算列数,再按四段填充写出输出。
展开练习 3 答案
cols = ⌈5/3⌉ = 2,先建 3×2 全 *。上行 (0,0)(0,1)=1 2 → top=1;右列 (1,1)(2,1)=3 4 → right=0;下行 (2,0)=5 → num=6 > 5 停止。输出 1 2 / * 3 / 5 4(第 1 行第 0 列保持 *)。做错最常见的原因:下行从右到左时忘了 right 已经收缩到 0,多填了一格。
练习 4(独立实现):不看参考程序,写出 in_bounds 与「返回合法四邻列表」的 neighbors(r, c),让下面的断言通过(m=n=3)。
展开练习 4 答案
边界函数与四邻的参考实现(自带断言)
Pythonm, n = 3, 3
DIRS = [(-1, 0), (1, 0), (0, -1), (0, 1)]
def in_bounds(r, c):
return 0 <= r < m and 0 <= c < n
def neighbors(r, c):
return [(r + dr, c + dc) for dr, dc in DIRS if in_bounds(r + dr, c + dc)]
assert len(neighbors(1, 1)) == 4 # 中心
assert len(neighbors(0, 1)) == 3 # 边
assert len(neighbors(0, 0)) == 2 # 角
assert neighbors(0, 0) == [(1, 0), (0, 1)]
assert not in_bounds(-1, 0) and not in_bounds(0, 3) # 负下标与右越界都判为出界neighbors 只保留通过 in_bounds 的坐标;顺序与 DIRS 一致(上、下、左、右)。把它接进 P2503:对每个格子取 neighbors(r, c) 里的值即可。
练习 5(迁移):用第 04 节补充学习里的映射 (i, j) → (j, n − 1 − i),写出 3×3 矩阵顺时针旋转 90° 后原来 (0,2)、(1,0)、(2,2) 三个位置各落到哪里,并用旋转后的矩阵 [[7,4,1],[8,5,2],[9,6,3]] 验证。
展开练习 5 答案
(0,2) → (2, 3−1−0) = (2,2):原值 3,旋转后 (2,2) 是 3 ✓;(1,0) → (0, 3−1−1) = (0,1):原值 4,旋转后 (0,1) 是 4 ✓;(2,2) → (2, 0):原值 9,旋转后 (2,0) 是 9 ✓。验证方法就是「按映射写入新矩阵,再与目标矩阵逐格比对」——基础题 H100053 可以直接用这套映射。
10 / 读题要求、基础加练与复习自评
两道题的要求对照,以及完成本课之后怎么复习
提交前把下表过一遍;题目页的题面与样例是最终依据。
| 项目 | P2503 统计监控 | P2529 螺旋数字矩阵 |
|---|---|---|
| 输入 | 第一行 m n(1 < m, n ≤ 20);随后 m 行、每行 n 个 0/1 | 一行两个整数 n m(0 < n, m < 999):数字个数、行数 |
| 行列含义 | m 是行数(「接下来 m 行」)、n 是每行个数 | m 行;列数 = ⌈n/m⌉ 由程序算出 |
| 规则 | 自身或上下左右任一相邻车位有车 → 打开 | 从左上角 1 起顺时针向内填 1…n;每行个数相同、列数尽可能少、优先填外圈、不够的格子单个 * |
| 输出 | 一个整数 | m 行,每行 cols 个元素,单个空格分隔 |
| 样例 | 题目页为准(自拟:中心一辆车 → 5) | 9 4 → 1 2 3 / * * 4 / 9 * 5 / 8 7 6 |
| 易错 | 行列接反;负下标;先取值后判边界 | 共享行引用;漏停止条件;数字用完仍继续填 |
题解入口:需要对照解法时,先展开本课第 07 节的两份完整参考程序;两道题的题目页另有思路与 Python / Java / C++ 参考代码,可在题目页查看。基础加练:本页下方「基础加练(选做)」的 4 道题(H100054 搜索二维矩阵 II、H100051 矩阵置零、H100053 旋转图像、H100052 螺旋矩阵)分别练坐标移动、先读后写、旋转映射、四条边界,与第 04、06 节一一对应。
复习与自评:本课算完成 = 两道必做题 P2503、P2529 都通过判题,并勾选全部六条「学习完成检查」;基础加练与复习题不影响完成状态。六条检查是自评,不改变题目的通过(AC)状态。复习时用三个问题自测:① 不看正文,写出方向数组和边界判定式,并说出漏掉下界会发生什么;② 不看表格,重算 n=9、m=4 的螺旋填充每一段的边界变量;③ 说出 P2503 里 m、n 各是行数还是列数、怎样用非方阵样例验证。答不出哪一条,就回到对应的节重读,再做第 09 节对应的练习。
基础加练(选做)
同一主题的 5 道基础题
这组题目采用标准输入输出(ACM 模式):程序从标准输入读取数据、把结果写到标准输出。它们难度低于必做题,适合在必做题之前热身,或在未通过时回来巩固;不计入本课完成标准。每道题给出练习重点、完成标准与提示;已在本站通过的题目会直接显示为已通过。
11 / 练习
按顺序完成本课的任务
必做题已通过 0/2 道
口算四邻:中心、边、角三类位置
代码自测自主练习练习重点:3×3 矩阵里 (1,1)、(0,1)、(0,0) 三个位置的合法四邻;预计用时:10 分钟
完成标准:能不看笔记说出三类位置各有几个合法邻居(4 / 3 / 2)
需要时查看提示
中心 4 邻全合法;边上的点 1 个方向出界剩 3 个;角上的点 2 个方向出界剩 2 个。推完后再不看资料写一遍判定式(0 <= r < m and 0 <= c < n)。第 04 节的表逐格列出了判定过程,第 09 节练习 4 有带断言的参考实现。
P2503 · 统计监控
必做任务 1练习重点:对每个车位判断:自身或四邻有车 → 监控器开;预计用时:22 分钟
完成标准:边角车位的判断不越界,答案与样例一致
需要时查看提示
两层循环遍历每个车位,检查自身和四邻(先调用边界函数(in_bounds)再取值)里有没有车,有就计数。也可以反过来:对每辆车把自身和四邻标进集合(set),最后数 set 大小——两种都对,选你能写稳的。第 05 节给了三组逐格手算。
P2529 · 螺旋数字矩阵
必做任务 2练习重点:列数 ⌈n/m⌉+四段螺旋填充+* 填充;预计用时:30 分钟
完成标准:能说出四个边界变量各在哪一段填完后收缩
需要时查看提示
列数(cols)= (n + m - 1) // m。先把 m×cols 的矩阵全填 *,再按上、下、左、右四个边界变量(top/bottom/left/right)四段螺旋填 1..n,填满 n 个就停。输出按题目要求的分隔格式来——先内容后排版,和 P2513 一个道理。第 06 节把 n=9、m=4 每一段的边界变量都列出来了。
提交结果
提交结果说明与处理方法
- WA
答案错误
优先检查三项:行列是否接反(用非方阵样例自测)、1 基坐标是否减一、Python 负下标是否取错数据——第 08 节的表给出了每种错误的具体输出
- PE
格式错误
矩阵输出行内分隔符与行末空格,对照样例逐字符查
- RE
运行错误
越界判断写在访问之后;螺旋边界收缩条件错误,导致下标越过有效范围
- TLE
超时
本课都是 O(m·n) 遍历,超时先查是否嵌套了多余的整表扫描
- AC
通过
把 in_bounds 函数和 DIRS 数组存进你的模板库——模块 3 网格搜索直接复用
12 / 学习完成检查
本课学习完成检查
完成本课需要:必做题全部通过,并勾选本课的全部学习完成检查;进阶练习、基础加练与复习题单独统计,不影响完成状态。登录后,勾选记录会保存到账号,并更新课程总览的完成状态。