LeetCode Hot100(14/100)——73. 矩阵置零

文章目录

一、题目概述

给定一个 m x n 的二维矩阵,如果某个元素为 0,则将其所在行与列的所有元素都设为 0。你需要 原地修改 输入矩阵(即不使用额外的矩阵存储结果)。

示例

输入:

复制代码
[
  [1,1,1],
  [1,0,1],
  [1,1,1]
]

输出:

复制代码
[
  [1,0,1],
  [0,0,0],
  [1,0,1]
]

说明:第 2 行第 2 列的元素为 0,因此第 2 行和第 2 列都需要置零。


二、思维导图

下面的思维导图展示了问题的整体思路,从暴力法到最优解的演进路线:
Set Matrix Zeroes
子问题分析
如果某元素为0 → 整行整列置0
解法演进
暴力解法
使用辅助数组
使用标记集合
原地标记法(最优)
复杂度
m*n
空间复杂度: 不同方法从 O(m+n) 降到 O(1)


三、解法一:暴力法(不推荐)

原理说明

当我们发现某个元素为 0 时,立即将其所在行和列全部设为 0。但是这样会有一个问题 ------ 更改后的 0 会影响后续判断。

改进思路

考虑用另一个矩阵来保存哪些位置需要被置 0,最后再统一修改。

实现步骤

  1. 遍历输入矩阵,记录所有为 0 的位置 (i, j)
  2. 再次遍历矩阵,将这些位置对应的行列全部置零。

时间与空间复杂度

  • 时间复杂度:O(m * n * (m + n))
  • 空间复杂度:O(1)(若直接修改),但逻辑会错误;若使用记录列表,约为 O(m * n)

四、解法二:使用辅助数组标记行列

原理说明

可以利用两个数组:

  • row[m]:标记每一行是否包含零;
  • col[n]:标记每一列是否包含零。

步骤流程





开始
初始化 row[], col[]
第一次遍历矩阵
元素为0?
标记行与列
继续遍历
第二次遍历矩阵
行或列标记为真?
设0
保持原值
结束

时间与空间复杂度

  • 时间复杂度:O(m * n)
  • 空间复杂度:O(m + n)

Java代码实现

java 复制代码
public void setZeroes(int[][] matrix) {
    int m = matrix.length;
    int n = matrix[0].length;
    boolean[] row = new boolean[m];
    boolean[] col = new boolean[n];

    // 记录哪些行和列需要置0
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (matrix[i][j] == 0) {
                row[i] = true;
                col[j] = true;
            }
        }
    }

    // 根据标记置0
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (row[i] || col[j]) {
                matrix[i][j] = 0;
            }
        }
    }
}

五、解法三:原地标记(最优方法)

核心思想

不使用额外空间,而直接利用矩阵的 第一行与第一列 作为标记数组的替代。

步骤解析

  1. 首先记录第一行和第一列是否本身含 0
  2. 从第二行第二列开始,如果某个元素为 0,就在所在的第一行和第一列位置标记为 0
  3. 再次遍历矩阵,当某行或某列的标记位为 0 时,就将该元素置零;
  4. 最后根据第一行和第一列的记录情况处理它们。

算法流程图



初始化
记录第一行是否有0
记录第一列是否有0
遍历剩余元素
元素为0?
标记第一行与第一列
继续遍历
根据标记置0
处理第一行列
结束

时间与空间复杂度

  • 时间复杂度:O(m * n)
  • 空间复杂度:O(1)

Java代码实现

java 复制代码
public void setZeroes(int[][] matrix) {
    int m = matrix.length;
    int n = matrix[0].length;
    boolean firstRowZero = false, firstColZero = false;

    // 检查第一行
    for (int j = 0; j < n; j++) {
        if (matrix[0][j] == 0) {
            firstRowZero = true;
            break;
        }
    }

    // 检查第一列
    for (int i = 0; i < m; i++) {
        if (matrix[i][0] == 0) {
            firstColZero = true;
            break;
        }
    }

    // 使用第一行和第一列标记零
    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            if (matrix[i][j] == 0) {
                matrix[i][0] = 0;
                matrix[0][j] = 0;
            }
        }
    }

    // 根据标记置零
    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;
            }
        }
    }

    // 处理第一行
    if (firstRowZero) {
        for (int j = 0; j < n; j++) matrix[0][j] = 0;
    }

    // 处理第一列
    if (firstColZero) {
        for (int i = 0; i < m; i++) matrix[i][0] = 0;
    }
}

六、各方法性能对比表

方法 时间复杂度 空间复杂度 是否原地修改
暴力法 O(mn(m+n))
辅助数组 O(m*n) O(m+n)
原地标记 O(m*n) O(1)

七、总结

  • 暴力法容易想到,但效率低;
  • 辅助数组平衡了性能与可读性,是较常见的做法;
  • 原地标记法是最优解:在不增加额外空间的条件下,完成高效的矩阵置零。

这道题体现了从"额外空间辅助"到"原地替代标记"的优化思维,也是很多二维数组操作题目的共性思路。

相关推荐
啊阿狸不会拉杆2 小时前
《数字信号处理》第 4 章-快速傅里叶变换 (FFT)
数据结构·人工智能·算法·机器学习·信号处理·数字信号处理·dsp
hrrrrb2 小时前
【算法设计与分析】算法概述
开发语言·python·算法
xqqxqxxq2 小时前
认识数据结构之——图 构建图与应用
数据结构·python·算法
求真求知的糖葫芦2 小时前
微波工程4.3节散射矩阵(S参数矩阵)学习笔记(上)(自用)
笔记·学习·矩阵·射频工程
FMRbpm2 小时前
邻接矩阵练习1--------LCP 07.传递信息
数据结构·c++·算法·leetcode·深度优先·新手入门
啊阿狸不会拉杆2 小时前
《数字信号处理》第 1 章 离散时间信号与系统
人工智能·算法·机器学习·信号处理·数字信号处理·dsp
ʚB҉L҉A҉C҉K҉.҉基҉德҉^҉大2 小时前
C++安全编程指南
开发语言·c++·算法
啊阿狸不会拉杆2 小时前
《数字信号处理》第 2 章 - z 变换与离散时间傅里叶变换(DTFT)
人工智能·算法·机器学习·信号处理·数字信号处理·dsp
老鼠只爱大米2 小时前
LeetCode经典算法面试题 #146:LRU 缓存(双向链表、线程安全等多种实现方案详细解析)
算法·leetcode·lru·lru缓存·双向链表