【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)

Problem: 73. 矩阵置零

题目:给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。

【LeetCode 热题 100】73. 矩阵置零------(解法一)空间复杂度 O(M + N)

文章目录

整体思路

这段代码旨在以 O(1) 的额外空间复杂度 解决 "矩阵置零" 问题。这是一个非常经典且巧妙的算法,它通过 复用矩阵自身的第一行和第一列 作为标记区域,来代替使用额外的 SetList

算法的整体思路可以分解为以下四个关键阶段:

  1. 阶段一:预先检查并标记第一行和第一列的状态

    • 由于第一行和第一列将被用作标记区域,它们自身是否需要被置零的状态需要被特殊处理,否则信息会丢失。
    • 算法使用两个布尔变量 rowBase0colBase0
      • colBase0:通过一个循环检查第一列 自身是否含有 0。如果含有,就将 colBase0 设为 true
      • rowBase0:通过另一个循环检查第一行 自身是否含有 0。如果含有,就将 rowBase0 设为 true
    • 这两个标志位保存了第一行和第一列最终是否需要被整体置零的"命运",为后续的标记操作解除了后顾之忧。
  2. 阶段二:使用第一行和第一列作为标记区域

    • 算法遍历矩阵的内部区域 (即从 i=1, j=1 开始)。
    • 当在内部区域发现一个零 matrix[i][j] == 0 时,它并不直接修改整行整列,而是将这个信息记录在对应的第一行和第一列上:
      • matrix[i][0] = 0; // 标记第 i 行需要被置零。
      • matrix[0][j] = 0; // 标记第 j 列需要被置零。
    • 这样,第一行和第一列就变成了存储标记的"哨兵"或"备忘录"。
  3. 阶段三:根据标记置零内部矩阵

    • 在完成标记后,算法再次遍历矩阵的内部区域i=1, j=1)。
    • 对于每个元素 matrix[i][j],它会检查其对应的行标记和列标记:
      • if (matrix[i][0] == 0 || matrix[0][j] == 0)
    • 如果第 i 行的标记(matrix[i][0])为 0,或者第 j 列的标记(matrix[0][j])为 0,就说明 matrix[i][j] 位于一个需要被置零的行或列,因此将其值设为 0。
  4. 阶段四:根据初始标志位处理第一行和第一列

    • 到目前为止,内部矩阵已经被正确处理。最后,轮到处理第一行和第一列自身了。
    • 这一步必须是最后一步,因为在此之前,第一行和第一列还承担着标记的角色。
    • 根据阶段一保存的 rowBase0colBase0 的值:
      • 如果 rowBase0true,则将整个第一行置零。
      • 如果 colBase0true,则将整个第一列置零。

通过这个精巧的四步流程,算法在不使用额外数组或集合的情况下,完成了矩阵置零的任务。

完整代码

java 复制代码
class Solution {
    /**
     * 将矩阵中包含 0 的元素的整行和整列都置为 0。
     * (空间复杂度 O(1) 的最优解)
     * @param matrix 一个 M x N 的整数矩阵
     */
    public void setZeroes(int[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        // rowBase0: 标记第一行本身是否需要被置零
        boolean rowBase0 = false;
        // colBase0: 标记第一列本身是否需要被置零
        boolean colBase0 = false;
        
        // 步骤 1: 检查第一列是否含有 0
        for (int i = 0; i < m; i++) {
            if (matrix[i][0] == 0) {
                colBase0 = true;
                break; // 找到一个即可
            }
        }
        // 步骤 1: 检查第一行是否含有 0
        for (int j = 0; j < n; j++) {
            if (matrix[0][j] == 0) {
                rowBase0 = true;
                break; // 找到一个即可
            }
        }
        
        // 步骤 2: 使用第一行和第一列作为标记区域,记录内部矩阵 (从[1,1]开始) 的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 列需要置零
                }
            }
        }

        // 步骤 3: 根据第一行和第一列的标记,置零内部矩阵
        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;
                }
            }
        }

        // 步骤 4: 最后,根据初始记录的标志位,处理第一行和第一列
        // 这一步必须在最后,以防过早破坏标记信息
        if (rowBase0) {
            for (int j = 0; j < n; j++) {
                matrix[0][j] = 0;
            }
        }

        if (colBase0) {
            for (int i = 0; i < m; i++) {
                matrix[i][0] = 0;
            }
        }
    }
}

时空复杂度

时间复杂度:O(M * N)

  1. 检查第一行/列 :两个独立的 for 循环,分别执行最多 M 次和 N 次。时间复杂度为 O(M + N)。
  2. 标记内部矩阵 :一个双层 for 循环,遍历 (M-1) * (N-1) 个元素。时间复杂度为 O(M * N)
  3. 置零内部矩阵 :另一个双层 for 循环,同样遍历 (M-1) * (N-1) 个元素。时间复杂度为 O(M * N)
  4. 处理第一行/列 :两个独立的 for 循环,分别执行 N 次和 M 次。时间复杂度为 O(M + N)。

综合分析

算法的总时间复杂度由几个部分组成,其中最高阶项是 O(M * N)。因此,最终的时间复杂度是 O(M * N)

空间复杂度:O(1)

  1. 主要存储开销 :该算法没有创建任何与输入规模 MN 成比例的新的数据结构。
  2. 辅助变量 :只使用了 m, n, rowBase0, colBase0, i, j 等几个常数数量的变量。

综合分析

算法所需的额外辅助空间是常数级别的,不随矩阵大小变化。因此,其空间复杂度为 O(1),这是该问题的最优空间解法。

相关推荐
慕y2742 分钟前
Java学习第十五部分——MyBatis
java·学习·mybatis
A__tao5 分钟前
SQL 转 Java 实体类工具
java·数据库·sql
喝可乐的布偶猫9 分钟前
Java类变量(静态变量)
java·开发语言·jvm
TDengine (老段)33 分钟前
TDengine STMT2 API 使用指南
java·大数据·物联网·时序数据库·iot·tdengine·涛思数据
喝可乐的布偶猫44 分钟前
韩顺平之第九章综合练习-----------房屋出租管理系统
java·开发语言·ide·eclipse
Code季风1 小时前
深入理解微服务中的服务注册与发现(Consul)
java·运维·微服务·zookeeper·架构·go·consul
光军oi1 小时前
java微服务(Springboot篇)——————IDEA搭建第一个Springboot入门项目
java·spring boot·微服务
future14122 小时前
C#每日学习日记
java·学习·c#
一个混子程序员2 小时前
SpringBoot自定义Schedule注解
java
心之语歌2 小时前
Java高效压缩技巧:ZipOutputStream详解
java·后端