题目
标题和出处
标题:矩阵置零
出处:73. 矩阵置零
难度
3 级
题目描述
要求
给定一个 m × n \texttt{m} \times \texttt{n} m×n 的矩阵,如果一个元素为 0 \texttt{0} 0,则将其所在行和列的所有元素都设为 0 \texttt{0} 0。
请使用原地算法。
示例
示例 1:
输入:
matrix
=
[[1,1,1],[1,0,1],[1,1,1]]
\texttt{matrix = [[1,1,1],[1,0,1],[1,1,1]]}
matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:
[[1,0,1],[0,0,0],[1,0,1]]
\texttt{[[1,0,1],[0,0,0],[1,0,1]]}
[[1,0,1],[0,0,0],[1,0,1]]
示例 2:
输入:
matrix
=
[[0,1,2,0],[3,4,5,2],[1,3,1,5]]
\texttt{matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]}
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]]
\texttt{[[0,0,0,0],[0,4,5,0],[0,3,1,0]]}
[[0,0,0,0],[0,4,5,0],[0,3,1,0]]
数据范围
- m = matrix.length \texttt{m} = \texttt{matrix.length} m=matrix.length
- n = matrix[0].length \texttt{n} = \texttt{matrix[0].length} n=matrix[0].length
- 1 ≤ m, n ≤ 200 \texttt{1} \le \texttt{m, n} \le \texttt{200} 1≤m, n≤200
- -2 31 ≤ matrix[i][j] ≤ 2 31 − 1 \texttt{-2}^\texttt{31} \le \texttt{matrix[i][j]} \le \texttt{2}^\texttt{31} - \texttt{1} -231≤matrix[i][j]≤231−1
进阶
- 一个直观的解决方案是使用 O(mn) \texttt{O(mn)} O(mn) 的额外空间,但这并不是一个好的解决方案。
- 一个简单的改进方案是使用 O(m + n) \texttt{O(m + n)} O(m + n) 的额外空间,但这仍然不是最好的解决方案。
- 你能想出一个仅使用常量空间的解决方案吗?
解法一
思路和算法
最直观的做法是找到矩阵中所有等于 0 0 0 的元素,对于每个元素 0 0 0,将其所在行和列的所有元素置零。
如果直接在原矩阵中将元素置零,则无法判断等于 0 0 0 的元素是原始值等于 0 0 0 还是被置零,因此需要创建辅助矩阵,辅助矩阵和原矩阵的大小相同,辅助矩阵中的每个元素表示原矩阵中的该元素是否置零。
遍历原矩阵,对于原矩阵中每个等于 0 0 0 的元素,将辅助矩阵中相应位置的相同行和相同列的元素都设为置零。然后再次遍历原矩阵和辅助矩阵,对于辅助矩阵中置零的位置,将原矩阵中相应位置的元素置零。
代码
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
boolean[][] zero = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == 0) {
for (int k = 0; k < n; k++) {
zero[i][k] = true;
}
for (int k = 0; k < m; k++) {
zero[k][j] = true;
}
}
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (zero[i][j]) {
matrix[i][j] = 0;
}
}
}
}
}
复杂度分析
-
时间复杂度: O ( m n ( m + n ) ) O(mn(m + n)) O(mn(m+n)),其中 m m m 和 n n n 分别是矩阵 matrix \textit{matrix} matrix 的行数和列数。需要遍历矩阵两次,第一次遍历需要将元素 0 0 0 所在行和列的所有元素标为置零,每个元素需要 O ( m + n ) O(m + n) O(m+n) 的处理时间,第二次遍历将矩阵中的标为置零的元素置零,每个元素需要 O ( 1 ) O(1) O(1) 的处理时间,因此总时间复杂度是 O ( m n ( m + n ) ) O(mn(m + n)) O(mn(m+n))。
-
空间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是矩阵 matrix \textit{matrix} matrix 的行数和列数。需要创建和原矩阵大小相同的辅助矩阵记录原矩阵中的每个元素是否置零。
解法二
思路和算法
解法一的时间复杂度和空间复杂度都较高,可以优化。
由于矩阵中每个元素 0 0 0 所在行和列的所有元素需要置零,因此只需要记录矩阵的每一行和每一列是否需要置零即可。
创建两个哈希表分别记录矩阵的每一行和每一列是否需要置零,遍历矩阵一次,对于每个等于 0 0 0 的元素,在两个哈希表中分别记录其所在行和列需要置零,遍历结束之后即可得到所有需要置零的行和列。然后再次遍历矩阵,对于每个元素,如果两个哈希表中至少有一个哈希表记录了该元素所在的行或列需要置零,则将该元素置零。
实现方面,可以用两个数组分别记录矩阵的每一行和每一列是否需要置零。
代码
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
boolean[] rows = new boolean[m];
boolean[] columns = new boolean[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == 0) {
rows[i] = true;
columns[j] = true;
}
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (rows[i] || columns[j]) {
matrix[i][j] = 0;
}
}
}
}
}
复杂度分析
-
时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是矩阵 matrix \textit{matrix} matrix 的行数和列数。需要遍历矩阵两次,第一次遍历需要将元素 0 0 0 所在行和列在两个哈希表中记录,每个元素需要 O ( 1 ) O(1) O(1) 的处理时间,第二次遍历将矩阵中的标为置零的元素置零,每个元素需要 O ( 1 ) O(1) O(1) 的处理时间,因此总时间复杂度是 O ( m n ) O(mn) O(mn)。
-
空间复杂度: O ( m + n ) O(m + n) O(m+n),其中 m m m 和 n n n 分别是矩阵 matrix \textit{matrix} matrix 的行数和列数。需要创建两个哈希表(或数组)分别记录矩阵的每一行和每一列是否需要置零,各需要 O ( m ) O(m) O(m) 和 O ( n ) O(n) O(n) 的空间。
解法三
思路和算法
解法二仍需要 O ( m + n ) O(m + n) O(m+n) 的额外空间。如果要将空间复杂度降低到 O ( 1 ) O(1) O(1),必须在矩阵内部记录每一行和每一列是否需要置零。
在矩阵内部记录置零信息,可以使用第 0 0 0 行和第 0 0 0 列记录。第 0 0 0 行的一个元素如果是 0 0 0,则表示该元素所在列需要置零;第 0 0 0 列的一个元素如果是 0 0 0,则表示该元素所在行需要置零。
如果直接修改矩阵的第 0 0 0 行和第 0 0 0 列的元素,则无法记录矩阵的第 0 0 0 行和第 0 0 0 列是否需要置零,因此需要使用两个变量分别记录矩阵的第 0 0 0 行和第 0 0 0 列是否需要置零。
矩阵置零的完整过程如下。
-
遍历矩阵的第 0 0 0 行和第 0 0 0 列,记录矩阵的第 0 0 0 行和第 0 0 0 列是否需要置零。
-
遍历矩阵的其余元素(指除了第 0 0 0 行和第 0 0 0 列的全部元素,下同),对于每个等于 0 0 0 的元素,将其所在行的第 0 0 0 列的元素和所在列的第 0 0 0 行的元素置零。
-
再次遍历矩阵的其余元素,对于每个元素,如果一个元素所在的行或列需要置零,则将该元素置零。
-
如果矩阵的第 0 0 0 行需要置零,则将矩阵的第 0 0 0 行元素全部置零;如果矩阵的第 0 0 0 列需要置零,则将矩阵的第 0 0 0 列元素全部置零。
如果矩阵的第 0 0 0 行或第 0 0 0 列的一个元素原本就是 0 0 0,则该元素所在行和列需要置零,上述解法同样适用于该情况。
代码
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
boolean rowZero = false, columnZero = false;
for (int j = 0; j < n; j++) {
if (matrix[0][j] == 0) {
rowZero = true;
break;
}
}
for (int i = 0; i < m; i++) {
if (matrix[i][0] == 0) {
columnZero = true;
break;
}
}
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 (rowZero) {
for (int j = 0; j < n; j++) {
matrix[0][j] = 0;
}
}
if (columnZero) {
for (int i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
}
}
复杂度分析
-
时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是矩阵 matrix \textit{matrix} matrix 的行数和列数。最多需要遍历矩阵中的每个元素两次。
-
空间复杂度: O ( 1 ) O(1) O(1)。