73. 矩阵置零

73. 矩阵置零 - 力扣(LeetCode)

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,或者你也可以理解为一个抽象栈之类的。其实这也是为什么方法三要求倒序遍历——当然我不想再记方法三了。