LC73 矩阵置零¶
讲解章节:序列与数组
一句话
借首行首列当标记位,把「哪些行列要清零」的记录压进矩阵自己。
解题思路¶
这题考什么¶
朴素做法的陷阱在于不能边扫边清:把某个 0 所在的行列就地刷成 0 之后, 这些新写进去的 0 会被后面的扫描当成原始的 0,继续触发它们所在行列的清零, 最后整个矩阵被染黑。所以必须先完整地记录要清哪些行、哪些列,再统一动手。
记录方式一路优化下来是三档:
最后一档的观察是:如果 matrix[i][j] 是 0,那么第 i 行和第 j 列最终都会被清空, 其中自然也包括 matrix[i][0] 和 matrix[0][j]。既然这两个格子横竖都要变成 0, 干脆提前把它们置 0,当作「第 i 行要清」「第 j 列要清」的标记—— 不会丢失任何还需要的信息。于是首列充当 row_zero 数组,首行充当 col_zero 数组。
唯一的冲突是 matrix[0][0]:它同时属于首行和首列,一个格子担不了两个标记。 解法是把首行、首列自身是否含 0 这两件事先单独存进两个布尔变量, 整个流程结束后再按它们处理首行首列。两个布尔变量是常数空间,不破坏 O(1)。 「用数据结构自己存元信息」这类原地技巧见 序列与数组。
数据规模与复杂度¶
1 <= m, n <= 200,-2^31 <= matrix[i][j] <= 2^31 - 1。 时间 O(m n):四趟扫描,其中两趟只走首行首列。除两个布尔变量外空间 O(1)。 规模最大 200 x 200 = 4e4 个格子,时间不是瓶颈;这题考的是空间与顺序。
坑在哪¶
- 首行首列自身的状态必须最先单独记下来。标记趟会把 matrix[i][0] 与 matrix[0][j] 写成 0,一旦先写了标记再回头看首行首列有没有 0, 就分不清那个 0 是原本就有的、还是自己刚打上去的标记。 样例 2 的 [[0,1,2,0],[3,4,5,2],[1,3,1,5]] 首行就带两个 0,正好考这一点。
- 标记趟与清零趟都从下标 1 开始扫,不碰首行首列。若标记趟把 i = 0 也算进去, matrix[0][j] 为 0 时会去写 matrix[0][0],污染首列的标记; 若清零趟把 i = 0 算进去,首行会被自己的标记提前刷掉, 后面读 matrix[0][j] 判断列标记时全是 0,整个矩阵被清空。
- 首行首列必须最后处理,而且要在内部格子全部清完之后。 提前把首行刷成 0,内部清零趟再读 matrix[0][j] 就全是 0 了,同样导致全盘染黑。
- 两个标记的读取要用 or:
matrix[i][0] == 0 or matrix[0][j] == 0。 用 and 会变成「行和列都被标记才清」,漏掉绝大多数该清的格子。 - 首行与首列的处理互不影响,因为此时内部格子已经定稿, 首行首列的值不再被任何人读取,两个 if 谁先谁后都行。
- 题面写的是
n == matrix[0].length,保证至少有一行一列, 不必担心 matrix 为空;但 m 或 n 为 1 的退化情形要能走通—— 此时内部的range(1, m)或range(1, n)是空循环, 全部工作由最后两个 if 完成,逻辑仍然正确。 - 返回值是 None,答案留在入参 matrix 里。签名的返回类型是 void,
判题机调用完之后回头读第 0 个参数。任何形如
matrix = ...的重新绑定 都不会被判题机看见,必须原地写。 - 严格比对:置零结果唯一,没有多解。
样例复核¶
matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]],期望 [[0,0,0,0],[0,4,5,0],[0,3,1,0]]。 m = 3,n = 4。
预扫首行:matrix[0][0] 与 matrix[0][3] 是 0 -> first_row_zero = True
预扫首列:matrix[0][0] 是 0 -> first_col_zero = True
标记趟(i, j 都从 1 起):内部 6 个格子 4 5 2 / 3 1 5 里没有 0,
一个标记也没打上,首行首列维持原样
清零趟:读 matrix[i][0] 与 matrix[0][j]。第 0 列的 matrix[1][0] = 3、
matrix[2][0] = 1 都非 0,行标记都没亮;
第 0 行的 matrix[0][1] = 1、matrix[0][2] = 2 非 0,
matrix[0][3] = 0 亮着 -> 第 3 列的内部格子被清空
此时矩阵变成 [[0,1,2,0],[3,4,5,0],[1,3,1,0]]
first_row_zero 为真 -> 首行整行清空
first_col_zero 为真 -> 首列整列清空
终态 [[0,0,0,0],[0,4,5,0],[0,3,1,0]]
与样例一致。注意第 3 列的清空靠的是原本就在 matrix[0][3] 的那个 0 充当了列标记——这正是「首行首列自身的 0 既是数据又是标记」需要预先记录的原因。