【算法精讲】LeetCode 73. 矩阵置零:原地标记的巧妙艺术
摘要:本文详解 LeetCode 73 题“矩阵置零”,在严格限制原地修改的前提下,利用矩阵首行首列作为标记位,避免额外空间开销。通过两遍遍历完成行列清零,时间 O(MN),空间 O(1),是空间优化思维的经典范例。
🧠 核心知识点:什么是“原地算法”?
在算法题中,“原地(In-place)”通常指:
- 不使用与输入规模成比例的额外空间。
- 允许使用常数个变量(如
i,j,flag等)。 - 可以直接修改输入数据结构本身。
对于二维矩阵问题,若要求“原地”,我们常需思考:
能否复用矩阵自身的某部分空间来存储中间状态?
本题正是这一思想的绝佳实践 —— 我们用第一行和第一列作为“标记区”,记录哪些行/列需要被置零。
🎯 题目引入:LeetCode 73. 矩阵置零
题目描述
给定一个 m x n 的矩阵 matrix,如果某个元素为 0,则将其所在整行和整列的所有元素都设为 0。请原地完成此操作。
约束条件:
- 不能使用额外数组记录哪些行/列要清零。
- 必须修改原矩阵,且空间复杂度为 。
示例 1:
输入: [[1,1,1],
[1,0,1],
[1,1,1]]
输出: [[1,0,1],
[0,0,0],
[1,0,1]]
示例 2:
输入: [[0,1,2,0],
[3,4,5,2],
[1,3,1,5]]
输出: [[0,0,0,0],
[0,4,5,0],
[0,3,1,0]]
💡 解题思路详解
❗ 为什么不能边遍历边置零?
如果你遇到一个 0 就立刻把它的行和列全改成 0,那么后续遍历时会误判这些新生成的 0 是原始数据,导致错误扩散!
✅ 正确做法:先标记,后执行。
🔍 关键洞察:用第一行和第一列当“记事本”
我们可以这样做:
-
第一步:检查第一行和第一列是否原本就有 0
- 因为我们要借用它们做标记,所以得提前记下它们自己是否需要被清零。
- 用两个布尔变量
first_row_has_zero和first_col_has_zero记录。
-
第二步:用第一行和第一列标记其他行列
- 遍历矩阵内部(从
(1,1)开始),如果发现matrix[i][j] == 0,就把matrix[i][0]和matrix[0][j]设为0,表示第i行和第j列需要清零。
- 遍历矩阵内部(从
-
第三步:根据标记清零内部区域
- 再次遍历内部区域,如果
matrix[i][0] == 0或matrix[0][j] == 0,就把matrix[i][j]设为0。
- 再次遍历内部区域,如果
-
第四步:处理第一行和第一列本身
- 如果第一步中记录过第一行/列有零,则将它们整个清零。
💻 代码实现与逐行解析
from typing import List
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
if not matrix or not matrix[0]:
return
m, n = len(matrix), len(matrix[0])
# Step 1: 检查第一行和第一列是否有0
first_row_has_zero = any(matrix[0][j] == 0 for j in range(n))
first_col_has_zero = any(matrix[i][0] == 0 for i in range(m))
# Step 2: 用第一行和第一列标记其他行列
for i in range(1, m):
for j in range(1, n):
if matrix[i][j] == 0:
matrix[i][0] = 0 # 标记第i行
matrix[0][j] = 0 # 标记第j列
# Step 3: 根据标记清零内部区域
for i in range(1, m):
for j in range(1, n):
if matrix[i][0] == 0 or matrix[0][j] == 0:
matrix[i][j] = 0
# Step 4: 处理第一行和第一列本身
if first_row_has_zero:
for j in range(n):
matrix[0][j] = 0
if first_col_has_zero:
for i in range(m):
matrix[i][0] = 0
⚠️ 易错点提醒
- 忘记单独处理第一行/列:因为它们既是“标记区”又是“待清零区”,必须提前记录其初始状态。
- 边界情况未考虑:空矩阵、单行/单列矩阵需特殊处理(虽然本题测试用例一般保证非空)。
- 标记冲突:确保只在内部区域进行标记,避免覆盖首行首列的原始信息。
🔄 对比其他解法
| 方法 | 时间复杂度 | 空间复杂度 | 是否原地 |
|---|---|---|---|
| 暴力法 | O(MN) | O(M+N) | 否 |
| 使用集合记录 | O(MN) | O(M+N) | 否 |
| 本题解法 | O(MN) | O(1) | 是 |
✅ 本题解法是空间最优解,也是面试高频考点!