LeetCode 73. 矩阵置零
题目描述
给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。
示例 1:
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]
示例 2:
输入: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]]
进阶:
- 一个直接的解决方案是使用
O(mn)的额外空间,但这并不是一个好的解法。 - 一个简单的改进方案是使用
O(m+n)的额外空间,但这仍然不是最优的。 - 你能想出一个仅使用常数额外空间的解决方案吗?
解题思路
本题要求原地 修改矩阵,即不能额外申请一个同样大小的矩阵来存储结果。我们可以利用矩阵的第一行 和第一列作为标记数组,来记录哪些行和列需要被置零。
核心思想
- 预处理:先判断第一行和第一列本身是否含有 0,并记录下这两个布尔值(因为之后第一行和第一列会被用作标记,原始信息会被覆盖)。
- 扫描内部 :遍历除第一行和第一列外的所有元素,如果发现某个元素为 0,则将该元素所在行的行首 (第一列对应位置)和所在列的列顶(第一行对应位置)也置为 0。这样,第一行和第一列就充当了"标记数组"。
- 置零内部:再次遍历内部元素,如果它所在行的行首或所在列的列顶为 0,则将该元素置为 0。
- 处理第一行和第一列:根据之前记录的布尔值,决定是否将第一行或第一列全部置零。
这样做只需要两个额外的布尔变量,空间复杂度为 O(1)。
算法步骤(结合代码)
- 获取矩阵的行数
m和列数n。 - 用
r0记录第一行是否原本包含 0,用c0记录第一列是否原本包含 0。 - 遍历
i从 1 到m-1,j从 1 到n-1:- 如果
matrix[i][j] == 0,则设置matrix[i][0] = 0和matrix[0][j] = 0。
- 如果
- 再次遍历内部元素(
i>=1, j>=1):- 如果
matrix[i][0] == 0或matrix[0][j] == 0,则matrix[i][j] = 0。
- 如果
- 如果
c0为真,则将第一列所有元素置零。 - 如果
r0为真,则将第一行所有元素置零。
代码实现(C++)
cpp
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
// 记录第一行和第一列是否原本有 0
bool r0 = ranges::contains(matrix[0], 0);
bool c0 = ranges::any_of(matrix, [](auto& row){ return row[0] == 0; });
// 使用第一行和第一列作为标记
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j] == 0) {
matrix[i][0] = 0; // 标记第 i 行
matrix[0][j] = 0; // 标记第 j 列
}
}
}
// 根据标记将内部元素置零
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;
}
}
}
// 如果第一列原本有 0,则整列置零
if (c0) {
for (auto& row : matrix) {
row[0] = 0;
}
}
// 如果第一行原本有 0,则整行置零
if (r0) {
ranges::fill(matrix[0], 0);
}
}
};
代码说明
ranges::contains(matrix[0], 0)和ranges::any_of是 C++20 提供的范围算法,用于检查第一行和第一列是否包含 0。如果编译器不支持 C++20,可以用传统的std::find和std::any_of替代。- 标记过程只遍历内部元素(
i>=1, j>=1),避免干扰第一行和第一列原本的信息(但我们已经提前备份了)。 - 最后分别处理第一列和第一行,确保不会漏掉或错误置零。
复杂度分析
- 时间复杂度 :
O(m * n),遍历了矩阵三次(标记、置零内部、处理第一列),常数倍,因此整体为线性。 - 空间复杂度 :
O(1),只使用了两个布尔变量,符合原地算法的要求。
总结
- 本题的关键在于用矩阵自身的第一行和第一列作为标记数组,从而避免开辟额外空间。
- 注意要提前记录第一行和第一列是否原本有 0,否则标记过程会覆盖掉这些原始信息,导致无法正确处理第一行和第一列本身。
- 这种"原地标记"的技巧在矩阵类问题中经常出现,值得熟练掌握。
扩展思考 :如果要求不能修改第一行和第一列本身,则必须使用O(m+n)的额外标记数组,但本题允许,所以上述解法是最优的。