【力扣100题】22. 矩阵置零

一、题目描述

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

示例 1:

复制代码
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]

示例 2:

复制代码
输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]

提示:

  • m == matrix.length
  • n == matrix[0].length
  • 1 <= m, n <= 200
  • -2^31 <= matrix[i][j] <= 2^31 - 1

进阶:

  • 一个直观的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案
  • 一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案
  • 你能想出一个仅使用常量空间的解决方案吗

二、解题思路总览

核心问题: 如何记录哪些行和列需要置零,同时不破坏原矩阵?

解决方案:两次遍历 + 额外数组记录

遍历 操作
第一次遍历 扫描整个矩阵,遇到 0 就记录该行该列需要置零
第二次遍历 根据记录,将对应行和列的所有元素置零
方案 时间复杂度 空间复杂度
暴力解(每个0都遍历行列) O(mn * (m+n)) O(1)
额外数组记录(本题) O(mn) O(m+n)
原地算法(进阶) O(mn) O(1)

三、完整代码

cpp 复制代码
class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();

        vector<int> row(m, 0);  // 记录哪些行需要置零
        vector<int> col(n, 0);  // 记录哪些列需要置零

        // 第一次遍历:扫描矩阵,记录需要置零的行和列
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    row[i] = 1;
                    col[j] = 1;
                }
            }
        }

        // 第二次遍历:根据记录,将对应行和列置零
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (row[i] || col[j]) {
                    matrix[i][j] = 0;
                }
            }
        }
    }
};

四、算法流程图

4.1 整体流程

复制代码
输入:matrix(m x n 矩阵)

[Step 1] 获取矩阵尺寸
         m = matrix.size()
         n = matrix[0].size()
         |
         v
[Step 2] 创建辅助数组
         row = vector<int>(m, 0)
         col = vector<int>(n, 0)
         |
         v
[Step 3] 第一次遍历:扫描并标记
         |
         v
    for i = 0 to m-1:
        for j = 0 to n-1:
            matrix[i][j] == 0 ?
              |是                  |否
              v                   v
          row[i] = 1          继续
          col[j] = 1              |
              |                   v
              +<---- 继续内层循环 --+
              |
              v
         内层循环结束
              |
              v
         回到外层循环或下一步
              |
              v
[Step 4] 第二次遍历:置零
              |
              v
    for i = 0 to m-1:
        for j = 0 to n-1:
            row[i] || col[j] ?
              |是                  |否
              v                   v
          matrix[i][j] = 0     继续(不变)
              |                   |
              v                   v
         【返回】            【返回】

4.2 第一次遍历(标记)详细流程

复制代码
外层循环:i = 0 to m-1
  内层循环:j = 0 to n-1
    |
    v
  matrix[i][j] == 0 ?
    |否
    v
  继续 j++

    |是
    v
  row[i] = 1
  col[j] = 1
  |
  v
  继续 j++

内层循环 j 结束后
  |
  v
i++ 继续外层循环

所有遍历完成后:
row 数组标记了所有含 0 的行
col 数组标记了所有含 0 的列

4.3 第二次遍历(置零)详细流程

复制代码
外层循环:i = 0 to m-1
  内层循环:j = 0 to n-1
    |
    v
  row[i] == 1 或 col[j] == 1 ?
    |是
    v
  matrix[i][j] = 0
    |
    v
  继续 j++

    |否(该位置不变)
    |
    v
  继续 j++

内层循环 j 结束后
  |
  v
i++ 继续外层循环

4.4 具体示例执行流程

复制代码
输入矩阵:
[[1, 1, 1],
 [1, 0, 1],
 [1, 1, 1]]

m = 3, n = 3
row = [0, 0, 0]
col = [0, 0, 0]

第一次遍历(标记):
  i=0, j=0: matrix[0][0]=1 != 0 → 跳过
  i=0, j=1: matrix[0][1]=1 != 0 → 跳过
  i=0, j=2: matrix[0][2]=1 != 0 → 跳过
  i=1, j=0: matrix[1][0]=1 != 0 → 跳过
  i=1, j=1: matrix[1][1]=0 == 0 → row[1]=1, col[1]=1
  i=1, j=2: matrix[1][2]=1 != 0 → 跳过
  i=2, j=0: matrix[2][0]=1 != 0 → 跳过
  i=2, j=1: matrix[2][1]=1 != 0 → 跳过
  i=2, j=2: matrix[2][2]=1 != 0 → 跳过

标记结果:
row = [0, 1, 0]
col = [0, 1, 0]

第二次遍历(置零):
  i=0, j=0: row[0]=0 且 col[0]=0 → 保持 1
  i=0, j=1: row[0]=0 但 col[1]=1 → 置零
  i=0, j=2: row[0]=0 且 col[2]=0 → 保持 1
  i=1, j=0: row[1]=1 → 置零
  i=1, j=1: row[1]=1 且 col[1]=1 → 置零
  i=1, j=2: row[1]=1 → 置零
  i=2, j=0: row[2]=0 且 col[0]=0 → 保持 1
  i=2, j=1: row[2]=0 但 col[1]=1 → 置零
  i=2, j=2: row[2]=0 且 col[2]=0 → 保持 1

输出矩阵:
[[1, 0, 1],
 [0, 0, 0],
 [1, 0, 1]]

五、逐行解析

5.1 创建辅助数组

cpp 复制代码
vector<int> row(m, 0);  // 记录哪些行需要置零,初始化全为 0
vector<int> col(n, 0);  // 记录哪些列需要置零,初始化全为 0

原理: 用两个一维数组分别记录需要置零的行和列。数组下标对应行号或列号,值为 1 表示需要置零。

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


5.2 第一次遍历:标记

cpp 复制代码
for (int i = 0; i < m; i++) {
    for (int j = 0; j < n; j++) {
        if (matrix[i][j] == 0) {
            row[i] = 1;
            col[j] = 1;
        }
    }
}

原理: 遍历整个矩阵,找到所有值为 0 的元素,将其所在行和列标记。

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


5.3 第二次遍历:置零

cpp 复制代码
for (int i = 0; i < m; i++) {
    for (int j = 0; j < n; j++) {
        if (row[i] || col[j]) {
            matrix[i][j] = 0;
        }
    }
}

原理: 再次遍历矩阵,如果当前元素所在行或列被标记过(row[i] || col[j] 为真),则将该元素置零。

逻辑: 只要行被标记 OR 列被标记,该元素就需要置零。


六、进阶:原地算法 O(1) 空间

6.1 核心思想

用矩阵的第一行和第一列作为标记数组,代替 row 和 col 的作用。

问题: 如何区分「原本就是 0」和「被置零后变成 0」?

解决方案: 在遍历前,先判断第一行和第一列是否需要置零,然后用它们作为标记。

6.2 进阶代码

cpp 复制代码
class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();

        // 判断第一行和第一列是否需要置零
        bool firstRowZero = false;
        bool 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;  // 标记第 i 行
                    matrix[0][j] = 0;  // 标记第 j 列
                }
            }
        }

        // 根据标记置零(跳过第一行和第一列)
        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;
            }
        }
    }
};

6.3 进阶流程图

复制代码
[Step 1] 判断第一行是否含 0
         firstRowZero = matrix[0][j] == 0 ?
         |
         v
[Step 2] 判断第一列是否含 0
         firstColZero = matrix[i][0] == 0 ?
         |
         v
[Step 3] 用第一行和第一列作为标记数组
         for i = 1 to m-1:
             for j = 1 to n-1:
                 matrix[i][j] == 0 ?
                   |是
                   v
               matrix[i][0] = 0  // 标记行
               matrix[0][j] = 0  // 标记列
         |
         v
[Step 4] 根据标记置零(排除第一行第一列)
         for i = 1 to m-1:
             for j = 1 to n-1:
                 matrix[i][0] == 0 或 matrix[0][j] == 0 ?
                   |是
                   v
               matrix[i][j] = 0
         |
         v
[Step 5] 处理第一行
         firstRowZero ?
           |是
           v
         matrix[0][j] = 0 for all j
         |
         v
[Step 6] 处理第一列
         firstColZero ?
           |是
           v
         matrix[i][0] = 0 for all i

6.4 三种方案对比

方案 时间复杂度 空间复杂度 特点
暴力解 O(mn * (m+n)) O(1) 每个0都遍历行列,时间太慢
额外数组(本题) O(mn) O(m+n) 两遍遍历,空间稍大
原地算法(进阶) O(mn) O(1) 用第一行/列作为标记,最优

七、复杂度分析

7.1 本题解法

指标 复杂度 说明
时间复杂度 O(m * n) 两次遍历矩阵
空间复杂度 O(m + n) row 数组 m 个,col 数组 n 个

7.2 进阶原地算法

指标 复杂度 说明
时间复杂度 O(m * n) 三次遍历矩阵
空间复杂度 O(1) 只用几个布尔变量

八、面试追问

问题 回答要点
为什么需要两次遍历? 第一次标记(记录哪些行/列要置零),第二次执行置零。如果边标记边置零,会导致后续判断出错
row[i]
空间复杂度能进一步优化吗? 可以,用矩阵第一行和第一列作为标记数组,实现 O(1) 空间
原地算法中 firstRowZero 的作用? 记录第一行本身是否需要置零(因为第一行会被用作标记,不能直接置零)
为什么原地算法要跳过第一行第一列? 第一行和第一列被用作标记数组,如果对它们执行置零操作,会丢失标记信息
原地算法如何恢复第一行第一列? 最后根据 firstRowZero 和 firstColZero 单独处理第一行和第一列
这个题有没有其他解法? 还有一种方案是设置一个 sentinel 值(如 INFINITY)来标记,但会改变矩阵范围外的状态,不推荐

九、相关题目

题号 题目 关键点
73 矩阵置零 本题
48 旋转图像 矩阵旋转
54 螺旋矩阵 矩阵遍历
59 螺旋矩阵 II 矩阵生成
289 生命游戏 原地算法,用位操作标记
相关推荐
05候补工程师1 小时前
【线性代数笔记】初等变换、正交化与特殊矩阵性质核心总结
经验分享·笔记·线性代数·考研·矩阵
Liangwei Lin1 小时前
LeetCode 78. 子集
数据结构·算法·leetcode
多加点辣也没关系1 小时前
数据结构与算法|第二十四章:算法思维总结与实战
算法·代理模式
炽烈小老头1 小时前
【每天学习一点算法 2026/05/11】排序链表
学习·算法·链表
wefg11 小时前
一些零散的算法
c++·算法
khalil10201 小时前
代码随想录算法训练营Day-48 单调栈02 | 42. 接雨水、84.柱状图中最大的矩形
数据结构·c++·算法·leetcode·单调栈·接雨水
Hcoco_me1 小时前
Ai:Agent/ infra / 智驾 / 推广算法 题库
人工智能·深度学习·算法·自动驾驶·剪枝
项目申报小狂人1 小时前
提出了一种带双向搜索的粒子群优化算法,一种基于双四元数运动优化的新型无人机3D路径规划方法及应用
算法·3d·无人机
驼同学.1 小时前
牛客网面试TOP101 - Python算法学习指南
python·算法·面试