1. 问题背景与核心需求
矩阵置零(Set Matrix Zeroes)是LeetCode Hot100系列中的经典题目(编号73),也是面试中频繁出现的考察点。这道题要求我们原地修改给定的m×n矩阵,当某个元素为0时,将其所在行和列的所有元素都设为0。看似简单的需求背后,隐藏着对空间复杂度优化的深度考量。
我第一次遇到这个问题时,直觉解法是创建辅助矩阵标记0的位置,但这会消耗O(mn)的额外空间。后来在面试中被追问"能否用常数空间解决",才意识到需要更巧妙的标记方法。这道题的价值在于训练我们如何在限制条件下重新思考数据存储方式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法思路分析与比较
2.1 暴力解法与空间优化路径
最直接的解法是:
- 遍历矩阵记录所有0元素的行列坐标
- 再次遍历矩阵,若当前行或列在记录中则置零
这种方法需要O(m+n)的额外空间存储行列标记。进一步优化方向是复用矩阵本身的空间:
python复制def setZeroes(matrix):
rows, cols = set(), set()
for i in range(len(matrix)):
for j in range(len(matrix[0])):
if matrix[i][j] == 0:
rows.add(i)
cols.add(j)
for i in range(len(matrix)):
for j in range(len(matrix[0])):
if i in rows or j in cols:
matrix[i][j] = 0
2.2 常数空间解法精要
真正的挑战在于O(1)空间解法。关键突破点是利用矩阵的首行和首列作为标记存储:
- 首先检查首行首列是否需要置零(用两个变量记录)
- 遍历非首行首列元素,发现0时在对应的首行和首列做标记
- 根据首行首列的标记,置零其他行列
- 最后处理首行首列
python复制def setZeroes(matrix):
m, n = len(matrix), len(matrix[0])
first_row_zero = any(matrix[0][j] == 0 for j in range(n))
first_col_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_zero:
for j in range(n):
matrix[0][j] = 0
if first_col_zero:
for i in range(m):
matrix[i][0] = 0
3. 关键实现细节与边界处理
3.1 标记顺序的重要性
必须优先处理首行首列的标记状态,否则后续标记会污染这两个标记区域。我曾在一个周赛版本中犯过先处理内部元素再检查首行首列的错误,导致整个矩阵被错误置零。
3.2 特殊矩阵的处理
对于单行或单列矩阵需要特殊处理。例如1×n矩阵只需遍历一次记录是否有0,然后决定是否整行置零。测试用例应包含:
- 全零矩阵
- 无零矩阵
- 首行/首列含零的矩阵
- 单个元素矩阵
3.3 标记值的选取
有些解法会使用特殊值(如float('inf'))标记,但这可能不适用于所有数据类型。最安全的方式还是利用矩阵自带的行列进行标记。
4. 复杂度分析与优化对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 辅助矩阵法 | O(mn) | O(mn) | 无空间限制时首选 |
| 行列标记法 | O(mn) | O(m+n) | 常规面试要求 |
| 原地标记法 | O(mn) | O(1) | 空间严格限制的场合 |
在实际面试中,建议先阐述暴力解法,然后逐步优化到常数空间解法。这能展示你的思维过程和对空间复杂度的敏感度。
5. 变种问题与扩展思考
5.1 多步置零问题
如果要求不是立即置零,而是允许在k步操作内完成,可以转化为图论中的覆盖问题。这类变种在Bloomberg的面试题中出现过。
5.2 稀疏矩阵优化
当矩阵非常稀疏时(零元素极少),可以只记录非零元素的位置,然后计算需要置零的行列交集。这在处理大型数据集时特别有用。
5.3 并行化处理
对于超大规模矩阵,可以考虑将矩阵分块后并行处理。此时需要注意块边缘的行列标记同步问题。
6. 实战技巧与调试建议
- 可视化调试:打印矩阵中间状态时,建议用格式化输出保持对齐:
python复制print('\n'.join([' '.join(map(str, row)) for row in matrix]))
-
边界测试:特别注意m=1或n=1的情况,这类edge case在面试中经常被用来考察代码鲁棒性。
-
标记清理:如果使用特殊值标记,最后必须恢复原始数据。我有次周赛因为忘记清理标记导致提交失败。
-
语言特性利用:在Python中可以使用any()/all()简化首行首列检查,但要注意这可能会稍微影响可读性。
这道题的经典之处在于它用简单的需求考察了多个维度的能力:空间优化意识、边界条件处理、标记技巧运用等。建议在理解解法后,尝试闭眼白板编程,直到能一次写对全部边界情况。这对我后来处理其他矩阵类题目(如旋转图像、螺旋矩阵等)都有很大帮助。
