LeetCode 73中等矩阵 · 标记
矩阵置零 图解题解
这道题到底在问什么
原地修改矩阵:若 matrix[i][j]==0,则第 i 行与第 j 列全部置 0。进阶要求只用常数额外空间。
- 输入
- [[1,2,3,4],[5,0,7,8],[9,10,11,12],[13,14,0,16]]
- 输出
- [[1,0,0,4],[0,0,0,0],[9,0,0,12],[0,0,0,0]]
最优解:一步一步想明白
- 3核心一句话:用第一行第一列当标记位记录哪些行列要清零,再统一置零,省下额外空间。
- 4阶段①:第一行、第一列马上要被拿来当「草稿纸」记标记,所以先单独记住它们本来含不含 0。本例里 firstRowZero=false、firstColZero=false(都不含,待会儿不用额外清它们自己)。
- 5阶段②:扫到 matrix[1][1]=0(红色)。先别急着清,去它的行首 (1,0) 和列首 (0,1) 各打个 0 当标记,记住「第 1 行、第 1 列都要清」。
- 6把行首 matrix[1][0] 置 0 当标记(浅橘)——表示第 1 行整行待清。
- 7把列首 matrix[0][1] 置 0 当标记(浅橘)——表示第 1 列整列待清。这个 0 本身的「清行清列」任务,就交给这两个标记记着了。
- 8阶段②:扫到 matrix[3][2]=0(红色)。先别急着清,去它的行首 (3,0) 和列首 (0,2) 各打个 0 当标记,记住「第 3 行、第 2 列都要清」。
- 9把行首 matrix[3][0] 置 0 当标记(浅橘)——表示第 3 行整行待清。
- 10把列首 matrix[0][2] 置 0 当标记(浅橘)——表示第 2 列整列待清。这个 0 本身的「清行清列」任务,就交给这两个标记记着了。
- 11阶段②扫描完毕。内部所有 0 都已经在它们的行首、列首留下了标记(浅橘共 4 个)。下面按标记统一清零。
- 12(1,1) 行首(1,0)、列首(0,1) 都标了 → 该清。它本来就是 0,标记落定为「已处理」(绿色)。
- 13(1,2)=7 → 查它的行首/列首:行首(1,0)、列首(0,2) 都标了 → 这一格要清零。
- 14(1,2) 置 0 完成(绿色)。
- 15(1,3)=8 → 查它的行首/列首:行首(1,0) 标了 → 这一格要清零。
- 16(1,3) 置 0 完成(绿色)。
- 17(2,1)=10 → 查它的行首/列首:列首(0,1) 标了 → 这一格要清零。
- 18(2,1) 置 0 完成(绿色)。
- 19(2,2)=11 → 查它的行首/列首:列首(0,2) 标了 → 这一格要清零。
- 20(2,2) 置 0 完成(绿色)。
- 21(3,1)=14 → 查它的行首/列首:行首(3,0)、列首(0,1) 都标了 → 这一格要清零。
- 22(3,1) 置 0 完成(绿色)。
- 23(3,2) 行首(3,0)、列首(0,2) 都标了 → 该清。它本来就是 0,标记落定为「已处理」(绿色)。
- 24(3,3)=16 → 查它的行首/列首:行首(3,0) 标了 → 这一格要清零。
- 25(3,3) 置 0 完成(绿色)。
- 26阶段④:firstRowZero=false → 第一行本来就没有 0,不用额外清它(它上面的标记 0 是「列待清」的记号,但本例第一行没真正落到要清的列上的值,保持)。
- 27firstColZero=false → 第一列本来没有 0,不用额外清它自己。至此全部处理完毕。
- 28最终结果:所有该清的行列都已清零。原来在 (1,1)、(3,2) 的两个 0,把第 1、3 行和第 1、2 列都拉成了 0。
⚠️ 容易写错的地方
✗ 错:边扫边清
✓ 对:先记标记,再统一清零
一边扫一边清会冒出新的 0,把不该清的格子也误判要清
✗ 错:忘了先记第一行/第一列本身是否含 0
✓ 对:动标记前先存 firstRow/firstCol 两个布尔
首行首列被借去当标记纸后,就分不清原来的 0 和后写的标记了
✗ 错:清零阶段从 (0,0) 开始
✓ 对:内部清零只扫 i≥1,j≥1,首行首列最后单独处理
从 0 开始会把标记位当数据清掉,逻辑乱套
完整代码(Python / C++ / Java)
Python
def setZeroes(matrix):
m, n = len(matrix), len(matrix[0])
first_row = any(matrix[0][j] == 0 for j in range(n))
first_col = any(matrix[i][0] == 0 for i in range(m))
for i in range(1, m): # ② 内部扫描打标记
for j in range(1, n):
if matrix[i][j] == 0:
matrix[i][0] = 0 # 行首标记
matrix[0][j] = 0 # 列首标记
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
if first_row: # ④ 收尾第一行/列
for j in range(n): matrix[0][j] = 0
if first_col:
for i in range(m): matrix[i][0] = 0C++
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
bool firstRow = false, firstCol = false;
for (int j = 0; j < n; j++) if (matrix[0][j] == 0) firstRow = true;
for (int i = 0; i < m; i++) if (matrix[i][0] == 0) firstCol = true;
for (int i = 1; i < m; i++) // ② 打标记
for (int j = 1; j < n; j++)
if (matrix[i][j] == 0) { matrix[i][0] = 0; matrix[0][j] = 0; }
for (int i = 1; i < m; i++) // ③ 按标记清零
for (int j = 1; j < n; j++)
if (matrix[i][0] == 0 || matrix[0][j] == 0) matrix[i][j] = 0;
if (firstRow) for (int j = 0; j < n; j++) matrix[0][j] = 0; // ④
if (firstCol) for (int i = 0; i < m; i++) matrix[i][0] = 0;
}
};Java
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
boolean firstRow = false, firstCol = false;
for (int j = 0; j < n; j++) if (matrix[0][j] == 0) firstRow = true;
for (int i = 0; i < m; i++) if (matrix[i][0] == 0) firstCol = true;
for (int i = 1; i < m; i++) // ② 打标记
for (int j = 1; j < n; j++)
if (matrix[i][j] == 0) { matrix[i][0] = 0; matrix[0][j] = 0; }
for (int i = 1; i < m; i++) // ③ 按标记清零
for (int j = 1; j < n; j++)
if (matrix[i][0] == 0 || matrix[0][j] == 0) matrix[i][j] = 0;
if (firstRow) for (int j = 0; j < n; j++) matrix[0][j] = 0; // ④
if (firstCol) for (int i = 0; i < m; i++) matrix[i][0] = 0;
}
}复杂度
时间
O(m·n)
每个格子最多被访问常数次(扫描 + 清零两遍)
空间
O(1)
只用 firstRow / firstCol 两个布尔量,标记借矩阵自身的首行首列
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 矩阵置零 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
O(m+n) 的写法和这个 O(1) 写法差在哪?+
O(m+n) 是另开两个布尔数组 rows[m]、cols[n] 分别记哪些行/列要清;O(1) 是把这两个数组「压」进矩阵自身的第一行和第一列,不再额外申请空间,代价是要多两个布尔变量处理首行首列的边界。
为什么不能简单地「遇到 0 就把整行整列清了」?+
因为清完会产生大量新的 0,后面再扫到这些新 0 又会去清它们的行列,最终整个矩阵被错误地清空。必须把「哪些行列要清」先记下来,扫描和清零两个阶段彻底分开。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 矩阵置零 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。