当前位置: 首页 > news >正文

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

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

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

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(M * N)
    • 空间复杂度:O(1)

整体思路

这段代码旨在以 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,则将整个第一列置零。

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

完整代码

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: 检查第一列是否含有 0for (int i = 0; i < m; i++) {if (matrix[i][0] == 0) {colBase0 = true;break; // 找到一个即可}}// 步骤 1: 检查第一行是否含有 0for (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),这是该问题的最优空间解法。

http://www.dtcms.com/a/267128.html

相关文章:

  • stm32的三种开发方式
  • Zigbee/Thread
  • 车载以太网-防火墙
  • 【深度学习】强化学习(Reinforcement Learning, RL)主流架构解析
  • 2025使用VM虚拟机安装配置Macos苹果系统下Flutter开发环境保姆级教程--下篇
  • React Native 开发环境搭建--mac--android--奔溃的一天
  • App爬虫实战篇-以华为真机手机爬取集换社的app为例
  • Pytest 测试发现机制详解:自动识别测试函数与模块
  • 在 Ubuntu 下配置 oh-my-posh —— 普通用户 + root 各自使用独立主题(共享可执行)
  • Redis Cluster 与 Sentinel 笔记
  • 文本方式和二进制方式打开文件的不同
  • Flutter 使用http库获取网络数据的方法(一)
  • Excel 数据透视表不够用时,如何处理来自多个数据源的数据?
  • MAX3485在MCU芯片AS32S601-485通信外设中的应用
  • 线程的礼让和加入
  • 1004、最大连续1的个数 III
  • SpringBatch使用介绍
  • 任务调度器(Scheduler)实现逻辑
  • Java 创建对象过程 JVM 内存分配并发安全笔记
  • JVM与JMM
  • Mysql底层专题(四)索引优化实战一
  • DeepSeek与诡秘之主
  • 在SoC数据加解密验证中使用 Python 的 gmssl 库
  • 03_性能优化:让软件呼吸更顺畅
  • 计算机网络(网页显示过程,TCP三次握手,HTTP1.0,1.1,2.0,3.0,JWT cookie)
  • 【网络协议安全】任务12:二层物理和单臂路由及三层vlanif配置方法
  • HarmonyOS:创建ArkTS卡片
  • 从零开始开发纯血鸿蒙应用之探析仓颉语言与ArkTS的差异
  • Vuex身份认证
  • 《C++初阶之类和对象》【经典案例:日期类】