各位题友大家好! 今天是 @负雪明烛 坚持日更的第 56 天。今天力扣上的每日一题是「73. 矩阵置零」。

解题思路

这个题目想要通过并不难,主要是题目分别有三个空间复杂度的要求:$O(MN)$,$O(M+N)$,$O(1)$。

首先介绍一下什么是原地算法:即在函数的输入矩阵上直接修改,而不是 return 一个矩阵。所以,力扣判定程序正确性的时候,仍然根据同一个 matrix 变量来判定。力扣判定的代码类似于:

  1. matrix = [输入矩阵]
  2. setZeroes(matrix)
  3. assert matrix 与预期矩阵相等

本题难点:如果在输入矩阵上原地修改把行列修改成了 0,那么再遍历到 0 的时候就不知道是原本数组中的 0 还是我们修改得到的 0。所有的解法都是为了解决该问题。

一、空间复杂度 $O(MN)$ 的算法

$O(MN)$ 的算法比较直接了当,有两种思路:

1.1 复制原数组

只要把输入的原始数组复制一份,那么根据 copy 出来的数组判断某个位置是否为 0,就是原始数组中的该位置是0。遇到 matrix_copy 的一个位置是 0,那么直接修改 matrix 的行列全部是 0。

  1. class Solution:
  2. def setZeroes(self, matrix: List[List[int]]) -> None:
  3. if not matrix or not matrix[0]:
  4. return
  5. M, N = len(matrix), len(matrix[0])
  6. matrix_copy = copy.deepcopy(matrix)
  7. for i in range(M):
  8. for j in range(N):
  9. if matrix_copy[i][j] == 0:
  10. for k in range(M):
  11. matrix[k][j] = 0
  12. for k in range(N):
  13. matrix[i][k] = 0
  1. class Solution {
  2. public:
  3. void setZeroes(vector<vector<int>>& matrix) {
  4. if (matrix.size() == 0 || matrix[0].size() == 0) return;
  5. vector<vector<int>> newM(matrix);
  6. const int M = matrix.size(), N = matrix[0].size();
  7. for (int r = 0; r < M; ++r) {
  8. for (int c = 0; c < N; ++c) {
  9. if (newM[r][c] == 0) {
  10. for (int j = 0; j < N; ++j) {
  11. matrix[r][j] = 0;
  12. }
  13. for (int i = 0; i < M; ++i) {
  14. matrix[i][c] = 0;
  15. }
  16. }
  17. }
  18. }
  19. }
  20. };
  • 时间复杂度:$O(MN*(M+N))$
  • 空间复杂度:$O(MN)$

    1.2 保存数组中 0 出现的位置

上面代码的思路是每次遇到 0 都去修改对应的行列。

这里的思路是对数组先做一次遍历,保存所有 0 出现的下标。最后对于每个 0 出现的位置都去修改一下其所在的行列。

  1. class Solution {
  2. public:
  3. void setZeroes(vector<vector<int>>& matrix) {
  4. if (matrix.size() == 0 || matrix[0].size() == 0) return;
  5. const int M = matrix.size(), N = matrix[0].size();
  6. queue<pair<int, int>> q;
  7. for (int r = 0; r < M; ++r) {
  8. for (int c = 0; c < N; ++c) {
  9. if (matrix[r][c] == 0) {
  10. q.push({r, c});
  11. }
  12. }
  13. }
  14. while (!q.empty()) {
  15. auto p = q.front(); q.pop();
  16. for (int j = 0; j < N; ++j) {
  17. matrix[p.first][j] = 0;
  18. }
  19. for (int i = 0; i < M; ++i) {
  20. matrix[i][p.second] = 0;
  21. }
  22. }
  23. }
  24. };
  • 时间复杂度:$O(MN*(M+N))$
  • 空间复杂度:$O(MN)$

二、空间复杂度 $O(M + N)$ 的算法

在空间复杂度是 $O(MN)$ 的解法中,我们需要对于每个出现的 0 都修改对应的行列。那么,如果同一个行(列)中如果出现了两个 0 ,则需要把该行(列)置两遍 0 。无论是空间还是时间复杂度,都不好。

一个容易得到的方法是:遍历一次矩阵,记录一下每行、列是否出现了 0;如果出现了 0,最终将此行列置为 0。

  1. class Solution:
  2. def setZeroes(self, matrix: List[List[int]]) -> None:
  3. if not matrix or not matrix[0]:
  4. return
  5. M, N = len(matrix), len(matrix[0])
  6. row, col = set(), set()
  7. for i in range(M):
  8. for j in range(N):
  9. if matrix[i][j] == 0:
  10. row.add(i)
  11. col.add(j)
  12. for i in range(M):
  13. for j in range(N):
  14. if i in row or j in col:
  15. matrix[i][j] = 0
  • 时间复杂度:$O(MN)$
  • 空间复杂度:$O(M+N)$

三、空间复杂度 $O(1)$ 的算法

空间复杂度为 $O(1)$,也就是说只能用常量的空间,难度就突然加大了。

方法的整体思想是:使用第 0 行和第 0 列来保存 matrix[1:M][1:N] 中是否出现了 0;那么 第 0 行和第 0 列的数据就不是输入的原始数据了。因此,为了知道第 0 行和第 0 列是否有 0,就必须提前统计。

这个方法可以分为四步走:
1. 统计 matrix 第 0 行 和 第 0 列 是否有 0,保存到 row0, col0 中;
2. 遍历 matrix[1:M][1:N] 看当前位置是否有 0,如果有 0,则把信息记录到 matrix 的第 0 行以及第 0 列中。
3. 根据 matrix 中第 0 行以及第 0 列的 0,将 matrix[1:M][1:N] 对应的行或者列全部置 0;
4. 根据 row0, col0 判断 第 0 行 和 第 0 列 是否需要全部置 0。

  1. class Solution:
  2. def setZeroes(self, matrix: List[List[int]]) -> None:
  3. if not matrix or not matrix[0]:
  4. return
  5. M, N = len(matrix), len(matrix[0])
  6. row0, col0 = False, False
  7. for i in range(M):
  8. if matrix[i][0] == 0:
  9. col0 = True
  10. for j in range(N):
  11. if matrix[0][j] == 0:
  12. row0 = True
  13. for i in range(1, M):
  14. for j in range(1, N):
  15. if matrix[i][j] == 0:
  16. matrix[i][0] = 0
  17. matrix[0][j] = 0
  18. for i in range(1, M):
  19. for j in range(1, N):
  20. if matrix[i][0] == 0 or matrix[0][j] == 0:
  21. matrix[i][j] = 0
  22. if row0:
  23. for j in range(N):
  24. matrix[0][j] = 0
  25. if col0:
  26. for i in range(M):
  27. matrix[i][0] = 0
  • 时间复杂度:$O(MN)$
  • 空间复杂度:$O(1)$

刷题心得

今天这个题,我个人认为能想到空间复杂度 $O(M+N)$ 的解法已经可以了。在实际工作中,如果写出空间复杂度为 $O(1)$ 的解法,可读性太差,有可能会被同事骂。


OK,以上就是 @负雪明烛 写的今天题解的全部内容了,如果你觉得有帮助的话,求赞、求关注、求收藏。如果有疑问的话,请在下面评论,我会及时解答。

关注我,你将不会错过我的精彩动画题解、面试题分享、组队刷题活动,进入主页 @负雪明烛 右侧有刷题组织,从此刷题不再孤单。

祝大家牛年大吉!AC 多多,Offer 多多!我们明天再见!