73. 矩阵置零
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int M = matrix.size();
int N = matrix.front().size();
vector<int> RowsToClear(M, 0);
vector<int> ColsToClear(N, 0);
// Mark.
for (int y = 0; y < M; ++y) {
for (int x = 0; x < N; ++x) {
if (matrix[y][x] != 0)
continue;
RowsToClear[y] = 1;
ColsToClear[x] = 1;
}
}
// Clear.
for (int y = 0; y < M; ++y) {
if (!RowsToClear[y])
continue;
for (int x = 0; x < N; ++x)
matrix[y][x] = 0;
}
for (int x = 0; x < N; ++x) {
if (!ColsToClear[x])
continue;
for (int y = 0; y < M; ++y)
matrix[y][x] = 0;
}
}
};
我不行了,上面这个空间确实是 \(\mathrm O(m+n)\),但是我平白无故地多遍历了一次矩阵。官解的方法是更对的,只需要遍历一次,然后在遍历的时候同时考察 RowsToClear 和 ColsToClear 就可以了,它们是一个逻辑或的关系。
这个只需要常量的方法确实很巧妙,直接原地把第一行、第一列用作标记数组,这样就只需要两个额外变量来标记第一行、第一列要不要清除了。
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int M = matrix.size();
int N = matrix.front().size();
bool ClearFirstRow = false;
bool ClearFirstCol = false;
// CHECK if first column/row needs to be cleared. Notice that we won't
// change the first column/row at this time. This can avoid us dealing with
// corner cases where we might need to check if x == 0 or y == 0.
for (int y = 0; y < M; ++y) {
if (matrix[y][0] == 0) {
ClearFirstCol = true;
break;
}
}
for (int x = 0; x < N; ++x) {
if (matrix[0][x] == 0) {
ClearFirstRow = true;
break;
}
}
// CHECK and MARK remaining cols and rows. Mind that the clear flag is ZERO
// instead of ONE. Since zeroes are all the same, while non-zeros are not.
for (int y = 1; y < M; ++y) {
for (int x = 1; x < N; ++x) {
if (matrix[y][x] == 0)
matrix[y][0] = matrix[0][x] = 0;
}
}
// Clear remaining columns and rows.
for (int y = 1; y < M; ++y) {
for (int x = 1; x < N; ++x) {
if (!matrix[y][0] || !matrix[0][x])
matrix[y][x] = 0;
}
}
// Lastly, clear first column/row.
if (ClearFirstCol) {
for (int y = 0; y < M; ++y)
matrix[y][0] = 0;
}
if (ClearFirstRow) {
for (int x = 0; x < N; ++x)
matrix[0][x] = 0;
}
}
};
但是要注意,这个常量方法的操作顺序必须是:1)先检查首行首列,此时暂时不要它们当作标记数组,也就是暂时不要修改它们的值;2)再标记除首行首列之外的部分,也就是下标从 1 开始,注意标注「需要清除」的 flag 应该是 0 而不是 1,因为是 0 的元素总是 0,不是 0 的元素各有各的不是;3)清除首行首列之外的部分;4)根据第一步当中得出的值选择是否要清除首行首列。
上述方法基本上就是一个 \(0\to1\to1\to0\) 的 pattern,或者你也可以理解为一个抽象栈之类的。其实这也是为什么方法三要求倒序遍历——当然我不想再记方法三了。