1. 项目背景与核心价值
"先飞的笨鸟"这个标题让我想起自己刚接触算法题时的经历。作为非科班出身的程序员,我深刻理解在LeetCode等算法平台上从零开始的艰辛。这个标题精准捕捉了算法学习者的普遍心态——即使基础薄弱,只要愿意提早出发、加倍努力,同样能够掌握算法精髓。
LeetCode第73题"矩阵置零"是一道经典的二维数组处理题目,考察对矩阵的遍历和原地修改能力。这道题在亚马逊、微软等大厂面试中出现频率较高,正是因为它能有效检验面试者对基础数据结构的掌握程度和编码实现能力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 题目分析与解题思路
2.1 问题描述重述
给定一个m×n的矩阵,如果某个元素为0,则将其所在行和列的所有元素都设为0。要求必须原地修改矩阵,不能使用额外的空间存储。
示例:
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
2.2 暴力解法与空间复杂度分析
最直观的解法是:
- 遍历矩阵,记录所有0元素的行列索引
- 再次遍历矩阵,将记录的行列全部置0
这种方法需要O(m+n)的额外空间存储行列标记。虽然能解决问题,但不符合题目"不使用额外空间"的要求。
2.3 优化思路突破点
关键突破在于利用矩阵本身的第一行和第一列来存储状态信息:
- 先检查第一行和第一列是否需要置零(用两个变量存储)
- 用第一行记录各列是否需要置零
- 用第一列记录各行是否需要置零
- 根据标记信息处理内部元素
- 最后处理第一行和第一列
这种巧妙的方法将空间复杂度降到了O(1),完全符合题目要求。
3. 完整代码实现与逐行解析
3.1 Python实现代码
python复制def setZeroes(matrix):
m, n = len(matrix), len(matrix[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))
# 使用第一行和第一列标记零的位置
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_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
3.2 关键代码解析
-
first_row_has_zero和first_col_has_zero:这两个布尔变量用于记录原始矩阵的第一行和第一列是否包含0,因为后续我们会用第一行和第一列作为标记空间。 -
第一个嵌套循环:遍历除第一行和第一列外的所有元素,发现0时,在对应的行首和列首做标记。
-
第二个嵌套循环:根据行首和列首的标记,将相应元素置0。
-
最后处理第一行和第一列:根据最初记录的标志决定是否将它们全部置0。
4. 复杂度分析与优化对比
4.1 时间复杂度分析
该算法需要四次遍历矩阵:
- 检查第一行和第一列是否有0:O(m+n)
- 标记内部0的位置:O((m-1)(n-1)) ≈ O(mn)
- 根据标记置零:O((m-1)(n-1)) ≈ O(mn)
- 处理第一行和第一列:O(m+n)
总体时间复杂度为O(mn),这是最优的,因为至少需要访问每个元素一次。
4.2 空间复杂度分析
仅使用了常数个额外变量(first_row_has_zero, first_col_has_zero),空间复杂度为O(1),完全满足题目要求。
4.3 与其他解法的对比
| 解法类型 | 时间复杂度 | 空间复杂度 | 实现难度 |
|---|---|---|---|
| 暴力解法 | O(mn) | O(m+n) | 简单 |
| 集合标记 | O(mn) | O(m+n) | 中等 |
| 本解法 | O(mn) | O(1) | 较难 |
5. 常见错误与调试技巧
5.1 典型错误案例
-
忘记处理第一行和第一列:
只关注了内部元素的标记和处理,导致第一行和第一列的0没有正确传播。 -
标记与处理顺序错误:
如果在标记前就修改了第一行或第一列的值,会导致标记信息被破坏。 -
边界条件处理不当:
对于1×n或m×1的特殊矩阵,需要特别注意循环的边界条件。
5.2 调试建议
-
打印中间状态:
在标记完成后和处理完成后分别打印矩阵,验证标记是否正确。 -
小规模测试:
先用3×3的小矩阵测试,再逐步增大规模。 -
特殊用例测试:
测试第一行有0、第一列有0、全0矩阵等特殊情况。
6. 实际应用与扩展思考
6.1 实际应用场景
这种矩阵处理技术在图像处理中很常见。例如:
- 图像二值化处理时清除特定行列
- 电子表格软件中清除满足条件的行列
- 游戏开发中地图区块的批量更新
6.2 扩展思考题
- 如果允许使用O(m+n)空间,但要求只能遍历矩阵一次,如何实现?
- 如果矩阵非常大,无法一次性装入内存,如何分块处理?
- 如果要求同时支持将0所在的行列设置为其他特定值,如何修改算法?
7. 个人学习心得
作为"先飞的笨鸟",我在刷这道题时经历了三个阶段:
- 最初用暴力解法,虽然AC了但知道不是最优解
- 尝试用集合存储行列信息,空间复杂度还是不够理想
- 最后通过画图分析,才想到利用第一行和第一列作为标记空间的巧妙方法
这个过程让我深刻体会到:
- 算法能力的提升需要时间和耐心
- 画图辅助分析往往能带来突破
- 满足特殊约束(如O(1)空间)需要创造性思维
对于算法初学者,我的建议是:
- 不要害怕从基础题目开始
- 每道题至少思考30分钟再查看答案
- 理解后尝试用不同方法实现
- 定期复习做过的题目
