LeetCode 热题 100-----18.矩阵置零

一、前置知识

在做这道题之前,我们需要先掌握几个基础概念,哪怕你完全没接触过算法、没学过矩阵,看完这部分也能轻松跟上后续内容。

1. 什么是矩阵?

矩阵就是"二维数组",简单说就是"表格"------有行、有列,每个格子里放一个数字。

比如题目中的输入 matrix = [[1,1,1],[1,0,1],[1,1,1]],就是一个 3 行 3 列的矩阵:

第 0 行(第一行):[1, 1, 1]

第 1 行(第二行):[1, 0, 1]

第 2 行(第三行):[1, 1, 1]

这里要注意:计算机里的"索引"(行号、列号)都是从 0 开始的,不是从 1 开始的,就像我们数数从 0 起步一样,记好这个细节,后面看代码不会乱。

2. 矩阵的遍历(怎么逐个看矩阵里的数字)

要处理矩阵里的每个数字,需要用"双重循环"------外层循环管"行",内层循环管"列"。

比如:用 i 表示"当前行号",用 j 表示"当前列号",先固定 i(比如先看第 0 行),然后让 j 从 0 到 列数-1(看完第 0 行的所有列),再让i 加 1(看第 1 行),重复这个过程,就能看完矩阵里所有的数字。

举个例子:遍历 3x3 矩阵,顺序是:(0,0) → (0,1) → (0,2) → (1,0) → (1,1) → (1,2) → (2,0) → (2,1) → (2,2),对应表格里的每个格子。

3. 原地算法(题目强制要求)

原地算法的核心:不创建和原矩阵一样大的新矩阵,直接修改题目给的原始矩阵。

简单说:题目给你一个矩阵 matrix,你不能再做一个和它一样大的矩阵(比如 new_matrix = [[0]*n for _ in range(m)])来存结果,必须直接改 matrix 里面的数字,这样做的目的是节省内存。

题目里函数返回值是 None(Python)或 void(C++),就是告诉你"不用返回新矩阵,改原始矩阵就行"。

4. 空间复杂度(衡量算法占用内存的多少)

空间复杂度是算法额外占用的内存大小,数值越小越好,这道题的进阶要求是"常量空间"(占用内存固定,和矩阵大小无关)。我们分三种情况理解,对应后面的三种解法:

  • O(mn):额外创建一个和原矩阵一样大的矩阵(m 行 n 列),比如暴力解法,占用内存最多;

  • O(m+n):额外创建两个一维数组(一个存需要置零的行,一个存需要置零的列),占用内存中等;

  • O(1):只用到几个临时变量(比如一个布尔值、一个整数),不创建任何数组,占用内存最少,是最优解。

补充:m 是矩阵的行数,n 是矩阵的列数(比如 3x3 矩阵,m=3,n=3)。

5. Python 和 C++ 中二维数组的基础操作(必看,否则看不懂代码)

Python 部分

  • 获取矩阵行数:m = len(matrix)(matrix 是一个列表,里面的每个元素都是一行,所以列表的长度就是行数);

  • 获取矩阵列数:n = len(matrix[0])(matrix[0] 是第一行,第一行的长度就是每一行的列数,矩阵每一行列数都一样);

  • 访问矩阵中第 i 行第 j 列的元素:matrix[i][j]

  • 复制矩阵:temp = [row[:] for row in matrix](row[:] 是复制每一行,避免修改 temp 时影响原 matrix)。

C++ 部分

  • 获取矩阵行数:int m = matrix.size();(matrix 是 vector<vector<int>> 类型,size() 返回它的元素个数,每个元素是一行);

  • 获取矩阵列数:int n = matrix[0].size();(matrix[0] 是第一行,size() 返回第一行的元素个数,即列数);

  • 访问矩阵中第 i 行第 j 列的元素:matrix[i][j]

  • 复制矩阵:vector<vector<int>> temp = matrix;(vector 赋值会自动复制所有元素,修改 temp 不影响原 matrix);

  • vector 初始化:vector<bool> row(m, false);(创建一个长度为 m 的 bool 类型数组,所有元素默认是 false)。

6. 关键坑点(避免做错的核心)

绝对不能"边遍历边置零"!比如你遍历到 matrix[i][j] = 0,直接把第 i 行、第 j 列都改成 0,那么后面遍历到这些新改的 0 时,会误以为它们是"原始的 0",导致不该置零的行和列也被置零,最终结果错误。

比如:矩阵 [[1,1,1],[1,0,1],[1,1,1]],如果直接边遍历边置零,当遇到 (1,1) 是 0,把第 1 行、第 1 列置零后,矩阵变成 [[1,0,1],[0,0,0],[1,0,1]],这时候再遍历 (0,1) 这个新的 0,会把第 0 行、第 1 列再置零(虽然结果对,但逻辑错了,换个矩阵就会出错)。所以必须先"标记"需要置零的行和列,再统一置零。

二、题目解析(再明确一遍题目要求)

给定一个 m x n 的矩阵(二维数组),如果矩阵中任意一个元素是 0,就把这个元素所在的"整行"和"整列"的所有元素都改成 0。要求:必须用原地算法,不能创建新的大矩阵。

示例 1:

输入:matrix = [[1,1,1],[1,0,1],[1,1,1]] → 有一个 0 在 (1,1) 位置

输出:[[1,0,1],[0,0,0],[1,0,1]] → 第 1 行全置零,第 1 列全置零

示例 2:

输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]] → 0 在 (0,0) 和 (0,3) 位置

输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]] → 第 0 行全置零,第 0 列、第 3 列全置零

三、三种解法(从暴力到最优,逐一看懂)

我们按照"暴力解法(入门,好懂)→ 优化解法(面试基础)→ 最优解法(面试进阶)"的顺序讲解,每种解法都包含:核心思路、Python 代码(逐行注释)、C++ 代码(逐行注释)、运行流程(跟着走)、优缺点。

解法一:暴力解法(O(mn) 空间,入门首选)

核心思路

既然不能边遍历边置零,那我们就先"复制一份原始矩阵",遍历复制的矩阵(temp),遇到 0 就去修改"原始矩阵(matrix)"的对应行和列。这样复制的矩阵里的 0 都是原始的 0,不会被新置的 0 干扰。

步骤:1. 复制原始矩阵 → 2. 遍历复制矩阵,标记并修改原始矩阵 → 3. 完成置零。

Python 代码(逐行注释,能看懂每一句)

python 复制代码
from typing import List  # 导入List类型,用于指定matrix的类型(可理解为:告诉计算机matrix是二维列表)

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        函数说明(必看):
        - 这个函数不需要返回任何值(返回None)
        - 要求直接修改输入的matrix(原地算法)
        - matrix: 输入的二维矩阵(题目给的原始矩阵)
        """
        # 1. 获取矩阵的行数m和列数n
        m = len(matrix)  # len(matrix)返回matrix的元素个数,每个元素是一行,所以m是行数
        n = len(matrix[0])  # matrix[0]是第一行,len(matrix[0])是第一行的元素个数,即列数n
        
        # 2. 复制原始矩阵temp,用于后续遍历(避免修改原矩阵时干扰判断)
        # row[:] 表示复制每一行的所有元素,这样修改temp不会影响matrix
        temp = [row[:] for row in matrix]
        
        # 3. 遍历复制的矩阵temp,找所有的0,然后修改原矩阵matrix
        # 外层循环:遍历每一行,i是当前行号(从0到m-1)
        for i in range(m):
            # 内层循环:遍历当前行的每一列,j是当前列号(从0到n-1)
            for j in range(n):
                # 当temp中当前位置(i,j)是0时,说明原矩阵对应位置也是0,需要置零对应行和列
                if temp[i][j] == 0:
                    # 3.1 置零原矩阵的第i行(把第i行所有列的元素都改成0)
                    # k是列号,从0到n-1,逐个修改第i行的每个元素
                    for k in range(n):
                        matrix[i][k] = 0  # 把第i行第k列的元素改成0
                    # 3.2 置零原矩阵的第j列(把第j列所有行的元素都改成0)
                    # k是行号,从0到m-1,逐个修改第j列的每个元素
                    for k in range(m):
                        matrix[k][j] = 0  # 把第k行第j列的元素改成0

# 主函数(测试代码,可直接运行,包含示例和自定义输入)
if __name__ == "__main__":
    # 实例化Solution类(可理解为:创建一个"解题工具")
    solution = Solution()
    
    # 测试示例1
    print("=== 测试示例1 ===")
    matrix1 = [[1,1,1],[1,0,1],[1,1,1]]  # 示例1输入
    print("输入矩阵:")
    # 打印输入矩阵(逐行打印,更直观)
    for row in matrix1:
        print(row)
    solution.setZeroes(matrix1)  # 调用置零函数,直接修改matrix1
    print("输出矩阵:")
    for row in matrix1:
        print(row)  # 打印修改后的矩阵(应该是[[1,0,1],[0,0,0],[1,0,1]])
    
    # 测试示例2
    print("\n=== 测试示例2 ===")
    matrix2 = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]  # 示例2输入
    print("输入矩阵:")
    for row in matrix2:
        print(row)
    solution.setZeroes(matrix2)
    print("输出矩阵:")
    for row in matrix2:
        print(row)  # 打印修改后的矩阵(应该是[[0,0,0,0],[0,4,5,0],[0,3,1,0]])
    
    # 自定义测试(自己输入一个矩阵,测试算法)
    print("\n=== 自定义测试 ===")
    # 自定义一个4x4矩阵,包含多个0
    matrix3 = [[1,2,3,4],[5,0,7,8],[9,10,11,12],[13,14,15,0]]
    print("输入矩阵:")
    for row in matrix3:
        print(row)
    solution.setZeroes(matrix3)
    print("输出矩阵:")
    for row in matrix3:
        print(row)  # 预期输出:[[1,0,3,0],[0,0,0,0],[9,0,11,0],[0,0,0,0]]

C++ 代码(逐行注释,能看懂每一句)

cpp 复制代码
#include <vector>  // 导入vector容器(可理解为:用于创建数组的工具)
#include <iostream>  // 导入输入输出工具(用于打印矩阵)
using namespace std;  // 简化代码,不用每次写std::

class Solution {
public:
    // 函数说明:
    // - void:表示函数不返回任何值(对应Python的None)
    // - vector<vector<int>& matrix:输入的二维矩阵,&表示"引用",修改matrix会直接修改原始矩阵(原地算法)
    void setZeroes(vector<vector<int>>& matrix) {
        // 1. 获取矩阵的行数m和列数n
        int m = matrix.size();  // matrix.size()返回矩阵的行数(vector的元素个数,每个元素是一行)
        int n = matrix[0].size();  // matrix[0].size()返回第一行的元素个数,即列数n
        
        // 2. 复制原始矩阵temp,用于后续遍历(避免修改原矩阵时干扰判断)
        vector<vector<int>> temp = matrix;  // 直接赋值,vector会自动复制所有元素
        
        // 3. 遍历复制的矩阵temp,找所有的0,然后修改原矩阵matrix
        // 外层循环:遍历每一行,i是当前行号(从0到m-1)
        for (int i = 0; i < m; i++) {
            // 内层循环:遍历当前行的每一列,j是当前列号(从0到n-1)
            for (int j = 0; j < n; j++) {
                // 当temp中当前位置(i,j)是0时,置零原矩阵的第i行和第j列
                if (temp[i][j] == 0) {
                    // 3.1 置零原矩阵的第i行
                    for (int k = 0; k < n; k++) {
                        matrix[i][k] = 0;  // 把第i行第k列的元素改成0
                    }
                    // 3.2 置零原矩阵的第j列
                    for (int k = 0; k < m; k++) {
                        matrix[k][j] = 0;  // 把第k行第j列的元素改成0
                    }
                }
            }
        }
    }
};

// 主函数(测试代码,可直接运行,包含示例和自定义输入)
int main() {
    // 实例化Solution类(创建解题工具)
    Solution solution;
    
    // 测试示例1
    cout << "=== 测试示例1 ===" << endl;
    vector<vector<int>> matrix1 = {{1,1,1},{1,0,1},{1,1,1}};  // 示例1输入
    cout << "输入矩阵:" << endl;
    // 打印输入矩阵(逐行打印)
    for (int i = 0; i < matrix1.size(); i++) {
        for (int j = 0; j < matrix1[0].size(); j++) {
            cout << matrix1[i][j] << " ";  // 打印每个元素,加空格分隔
        }
        cout << endl;  // 每打印一行换行
    }
    solution.setZeroes(matrix1);  // 调用置零函数,直接修改matrix1
    cout << "输出矩阵:" << endl;
    for (int i = 0; i < matrix1.size(); i++) {
        for (int j = 0; j < matrix1[0].size(); j++) {
            cout << matrix1[i][j] << " ";
        }
        cout << endl;
    }
    
    // 测试示例2
    cout << "\n=== 测试示例2 ===" << endl;
    vector<vector<int>> matrix2 = {{0,1,2,0},{3,4,5,2},{1,3,1,5}};  // 示例2输入
    cout << "输入矩阵:" << endl;
    for (int i = 0; i < matrix2.size(); i++) {
        for (int j = 0; j < matrix2[0].size(); j++) {
            cout << matrix2[i][j] << " ";
        }
        cout << endl;
    }
    solution.setZeroes(matrix2);
    cout << "输出矩阵:" << endl;
    for (int i = 0; i < matrix2.size(); i++) {
        for (int j = 0; j < matrix2[0].size(); j++) {
            cout << matrix2[i][j] << " ";
        }
        cout << endl;
    }
    
    // 自定义测试(自己输入一个矩阵,测试算法)
    cout << "\n=== 自定义测试 ===" << endl;
    vector<vector<int>> matrix3 = {{1,2,3,4},{5,0,7,8},{9,10,11,12},{13,14,15,0}};
    cout << "输入矩阵:" << endl;
    for (int i = 0; i < matrix3.size(); i++) {
        for (int j = 0; j < matrix3[0].size(); j++) {
            cout << matrix3[i][j] << " ";
        }
        cout << endl;
    }
    solution.setZeroes(matrix3);
    cout << "输出矩阵:" << endl;
    for (int i = 0; i < matrix3.size(); i++) {
        for (int j = 0; j < matrix3[0].size(); j++) {
            cout << matrix3[i][j] << " ";
        }
        cout << endl;
    }
    
    return 0;  // 主函数结束标志
}

运行流程(跟着走,以示例1为例)

示例1输入:matrix = [[1,1,1],[1,0,1],[1,1,1]],m=3,n=3

  1. 复制矩阵:temp = [[1,1,1],[1,0,1],[1,1,1]](和原矩阵一样);

  2. 遍历temp:

    1. i=0(第0行):j=0→1→2,temp[0][j]都是1,不做操作;

    2. i=1(第1行):j=0,temp[1][0]=1;j=1,temp[1][1]=0 → 触发置零:

      • 置零第1行:k=0→1→2,matrix[1][0]=0、matrix[1][1]=0、matrix[1][2]=0 → 此时matrix变成[[1,1,1],[0,0,0],[1,1,1]];

      • 置零第1列:k=0→1→2,matrix[0][1]=0、matrix[1][1]=0、matrix[2][1]=0 → 此时matrix变成[[1,0,1],[0,0,0],[1,0,1]];

    3. i=2(第2行):j=0→1→2,temp[2][j]都是1,不做操作;

  3. 遍历结束,matrix就是最终结果[[1,0,1],[0,0,0],[1,0,1]]。

优缺点

  • ✅ 优点:逻辑最简单,完全不会出错,容易理解,适合入门;

  • ❌ 缺点:空间复杂度O(mn),占用内存极大(比如200x200的矩阵,就要额外创建40000个元素的矩阵),实际开发和面试中绝对不推荐使用。

解法二:优化解法(O(m+n) 空间,面试基础写法)

核心思路

暴力解法的问题是"复制了整个矩阵",太浪费内存。其实我们不需要复制整个矩阵,只需要"标记哪些行、哪些列需要置零"就行------用两个一维数组,一个存需要置零的行,一个存需要置零的列。

步骤:1. 初始化两个标记数组(row标记行,col标记列)→ 2. 遍历原始矩阵,遇到0就标记对应行和列 → 3. 再次遍历原始矩阵,根据标记数组置零。

举个例子:row = [False, True, False] 表示"第1行需要置零";col = [False, True, False] 表示"第1列需要置零",后续遍历矩阵时,只要行或列被标记,就把元素改成0。

Python 代码(逐行注释)

python 复制代码
from typing import List

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        函数说明:原地修改矩阵,不返回值
        matrix: 输入的二维矩阵
        """
        # 1. 获取矩阵的行数m和列数n
        m = len(matrix)  # 行数m
        n = len(matrix[0])  # 列数n
        
        # 2. 初始化两个标记数组,默认都是False(表示"不需要置零")
        row = [False] * m  # row是长度为m的数组,row[i] = True → 第i行需要置零
        col = [False] * n  # col是长度为n的数组,col[j] = True → 第j列需要置零
        
        # 3. 第一步:遍历矩阵,标记需要置零的行和列
        # 外层循环遍历每一行,i是行号
        for i in range(m):
            # 内层循环遍历每一列,j是列号
            for j in range(n):
                # 如果当前元素是0,说明第i行和第j列都需要置零,标记对应的位置
                if matrix[i][j] == 0:
                    row[i] = True  # 标记第i行需要置零
                    col[j] = True  # 标记第j列需要置零
        
        # 4. 第二步:根据标记数组,统一置零原始矩阵
        # 再次遍历矩阵的每一个元素
        for i in range(m):
            for j in range(n):
                # 如果当前行被标记,或者当前列被标记,就把这个元素改成0
                if row[i] or col[j]:
                    matrix[i][j] = 0

# 主函数(测试代码,和解法一一致,包含示例和自定义输入)
if __name__ == "__main__":
    solution = Solution()
    
    # 测试示例1
    print("=== 测试示例1 ===")
    matrix1 = [[1,1,1],[1,0,1],[1,1,1]]
    print("输入矩阵:")
    for row in matrix1:
        print(row)
    solution.setZeroes(matrix1)
    print("输出矩阵:")
    for row in matrix1:
        print(row)
    
    # 测试示例2
    print("\n=== 测试示例2 ===")
    matrix2 = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
    print("输入矩阵:")
    for row in matrix2:
        print(row)
    solution.setZeroes(matrix2)
    print("输出矩阵:")
    for row in matrix2:
        print(row)
    
    # 自定义测试
    print("\n=== 自定义测试 ===")
    matrix3 = [[1,2,3,4],[5,0,7,8],[9,10,11,12],[13,14,15,0]]
    print("输入矩阵:")
    for row in matrix3:
        print(row)
    solution.setZeroes(matrix3)
    print("输出矩阵:")
    for row in matrix3:
        print(row)

C++ 代码(逐行注释)

cpp 复制代码
#include <vector>
#include <iostream>
using namespace std;

class Solution {
public:
    void setZeroes(vector<vector<int>& matrix) {
        // 1. 获取矩阵的行数m和列数n
        int m = matrix.size();
        int n = matrix[0].size();
        
        // 2. 初始化两个标记数组,默认都是false(不需要置零)
        vector<bool> row(m, false);  // row[i]为true → 第i行需要置零
        vector<bool> col(n, false);  // col[j]为true → 第j列需要置零
        
        // 3. 第一步:遍历矩阵,标记需要置零的行和列
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    row[i] = true;  // 标记第i行
                    col[j] = true;  // 标记第j列
                }
            }
        }
        
        // 4. 第二步:根据标记数组,统一置零
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 行或列被标记,就置零
                if (row[i] || col[j]) {
                    matrix[i][j] = 0;
                }
            }
        }
    }
};

// 主函数(测试代码,和解法一一致)
int main() {
    Solution solution;
    
    // 测试示例1
    cout << "=== 测试示例1 ===" << endl;
    vector<vector<int>> matrix1 = {{1,1,1},{1,0,1},{1,1,1}};
    cout << "输入矩阵:" << endl;
    for (int i = 0; i < matrix1.size(); i++) {
        for (int j = 0; j < matrix1[0].size(); j++) {
            cout << matrix1[i][j] << " ";
        }
        cout << endl;
    }
    solution.setZeroes(matrix1);
    cout << "输出矩阵:" << endl;
    for (int i = 0; i < matrix1.size(); i++) {
        for (int j = 0; j < matrix1[0].size(); j++) {
            cout << matrix1[i][j] << " ";
        }
        cout << endl;
    }
    
    // 测试示例2
    cout << "\n=== 测试示例2 ===" << endl;
    vector<vector<int>> matrix2 = {{0,1,2,0},{3,4,5,2},{1,3,1,5}};
    cout << "输入矩阵:" << endl;
    for (int i = 0; i < matrix2.size(); i++) {
        for (int j = 0; j < matrix2[0].size(); j++) {
            cout << matrix2[i][j] << " ";
        }
        cout << endl;
    }
    solution.setZeroes(matrix2);
    cout << "输出矩阵:" << endl;
    for (int i = 0; i < matrix2.size(); i++) {
        for (int j = 0; j < matrix2[0].size(); j++) {
            cout << matrix2[i][j] << " ";
        }
        cout << endl;
    }
    
    // 自定义测试
    cout << "\n=== 自定义测试 ===" << endl;
    vector<vector<int>> matrix3 = {{1,2,3,4},{5,0,7,8},{9,10,11,12},{13,14,15,0}};
    cout << "输入矩阵:" << endl;
    for (int i = 0; i < matrix3.size(); i++) {
        for (int j = 0; j < matrix3[0].size(); j++) {
            cout << matrix3[i][j] << " ";
        }
        cout << endl;
    }
    solution.setZeroes(matrix3);
    cout << "输出矩阵:" << endl;
    for (int i = 0; i < matrix3.size(); i++) {
        for (int j = 0; j < matrix3[0].size(); j++) {
            cout << matrix3[i][j] << " ";
        }
        cout << endl;
    }
    
    return 0;
}

运行流程(以示例2为例)

示例2输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]],m=3,n=4

  1. 初始化标记数组:row = [False, False, False],col = [False, False, False, False];

  2. 第一步:遍历矩阵,标记行和列: 标记后:row = [True, False, False](第0行需要置零),col = [True, False, False, True](第0列、第3列需要置零);

    1. i=0(第0行):

      • j=0:matrix[0][0] = 0 → row[0] = True,col[0] = True;

      • j=1:matrix[0][1] = 1 → 不操作;

      • j=2:matrix[0][2] = 2 → 不操作;

      • j=3:matrix[0][3] = 0 → row[0] = True(已标记,不变),col[3] = True;

    2. i=1(第1行):j=0→3,matrix[1][j]都不是0 → 不操作;

    3. i=2(第2行):j=0→3,matrix[2][j]都不是0 → 不操作;

  3. 第二步:根据标记置零:

    1. i=0(第0行):row[0] = True → 所有列都置零 → 第0行变成[0,0,0,0];

    2. i=1(第1行):row[1] = False,但col[0] = True、col[3] = True → 第0列和第3列置零 → 第1行变成[0,4,5,0];

    3. i=2(第2行):row[2] = False,但col[0] = True、col[3] = True → 第0列和第3列置零 → 第2行变成[0,3,1,0];

  4. 最终结果:[[0,0,0,0],[0,4,5,0],[0,3,1,0]],和示例一致。

优缺点

  • ✅ 优点:空间复杂度优化到O(m+n),占用内存少(比如200x200矩阵,只需要额外400个元素的数组),逻辑简单,面试中最基础、最常考的写法;

  • ❌ 缺点:仍需要额外创建两个数组,没有达到题目"常量空间"的进阶要求。

解法三:最优解法(O(1) 常量空间,面试进阶必背)

核心思路

进阶要求:仅用常量空间(不创建任何数组),那我们就"利用矩阵自身的空间"------用矩阵的第一行和第一列,代替解法二的两个标记数组。

关键逻辑:

  • 用 matrix[i][0](第i行的第一个元素)标记"第i行是否需要置零"(代替解法二的row数组);

  • 用 matrix[0][j](第j列的第一个元素)标记"第j列是否需要置零"(代替解法二的col数组);

  • 特殊问题:matrix[0][0] 既属于第一行,又属于第一列,如果用它标记,会分不清是"第一行需要置零"还是"第一列需要置零",所以我们用一个临时变量 col0 单独标记"第一列是否需要置零",matrix[0][0] 只标记"第一行是否需要置零"。

步骤(必记):

  1. 用临时变量 col0 标记第一列是否需要置零;

  2. 遍历矩阵(从第0行第0列开始),遇到0就用第一行、第一列和col0做标记;

  3. 从"第二行第二列"开始置零(避免覆盖第一行、第一列的标记);

  4. 单独处理第一行(根据matrix[0][0]的标记);

  5. 单独处理第一列(根据col0的标记)。

Python 代码(逐行注释,重点讲标记和置零顺序)

python 复制代码
from typing import List

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        函数说明:原地修改矩阵,常量空间O(1),最优解
        matrix: 输入的二维矩阵
        """
        # 1. 获取矩阵的行数m和列数n
        m = len(matrix)  # 行数m
        n = len(matrix[0])  # 列数n
        
        # 2. 定义临时变量col0,标记第一列是否需要置零(解决matrix[0][0]的冲突)
        # 初始值设为1(1表示不需要置零,0表示需要置零,用1/0更方便后续判断)
        col0 = 1
        
        # 3. 第一步:遍历矩阵,用第一行、第一列和col0做标记(核心步骤)
        # 遍历所有元素,i从0到m-1,j从0到n-1
        for i in range(m):
            for j in range(n):
                # 如果当前元素是0,进行标记
                if matrix[i][j] == 0:
                    # 3.1 标记第i行:把第i行的第一个元素(matrix[i][0])改成0
                    matrix[i][0] = 0
                    
                    # 3.2 标记第j列:分两种情况(避免matrix[0][0]冲突)
                    if j == 0:
                        # j=0表示当前元素在第一列,用col0标记第一列需要置零
                        col0 = 0
                    else:
                        # j≠0表示当前元素不在第一列,用第一行的第j个元素(matrix[0][j])标记
                        matrix[0][j] = 0
        
        # 4. 第二步:置零(从第二行第二列开始,避免覆盖标记)
        # 为什么从i=1、j=1开始?因为第一行和第一列是标记,先不动它们,否则标记会被覆盖
        for i in range(1, m):  # i从1开始(跳过第一行)
            for j in range(1, n):  # j从1开始(跳过第一列)
                # 如果当前行的标记是0(matrix[i][0] == 0),或者当前列的标记是0(matrix[0][j] == 0)
                if matrix[i][0] == 0 or matrix[0][j] == 0:
                    matrix[i][j] = 0  # 置零当前元素
        
        # 5. 第三步:单独处理第一行(根据matrix[0][0]的标记)
        # matrix[0][0] == 0 表示第一行需要置零
        if matrix[0][0] == 0:
            for j in range(n):  # 遍历第一行的所有列,全部置零
                matrix[0][j] = 0
        
        # 6. 第四步:单独处理第一列(根据col0的标记)
        # col0 == 0 表示第一列需要置零
        if col0 == 0:
            for i in range(m):  # 遍历第一列的所有行,全部置零
                matrix[i][0] = 0

# 主函数(测试代码,包含示例和自定义输入)
if __name__ == "__main__":
    solution = Solution()
    
    # 测试示例1
    print("=== 测试示例1 ===")
    matrix1 = [[1,1,1],[1,0,1],[1,1,1]]  # 示例1输入
    print("输入矩阵:")
    for row in matrix1:
        print(row)
    solution.setZeroes(matrix1)  # 调用最优解函数
    print("输出矩阵:")
    for row in matrix1:
        print(row)  # 预期输出:[[1,0,1],[0,0,0],[1,0,1]]
    
    # 测试示例2
    print("\n=== 测试示例2 ===")
    matrix2 = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]  # 示例2输入
    print("输入矩阵:")
    for row in matrix2:
        print(row)
    solution.setZeroes(matrix2)
    print("输出矩阵:")
    for row in matrix2:
        print(row)  # 预期输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]
    
    # 自定义测试
    print("\n=== 自定义测试 ===")
    matrix3 = [[1,2,3,4],[5,0,7,8],[9,10,11,12],[13,14,15,0]]
    print("输入矩阵:")
    for row in matrix3:
        print(row)
    solution.setZeroes(matrix3)
    print("输出矩阵:")
    for row in matrix3:
        print(row)  # 预期输出:[[1,0,3,0],[0,0,0,0],[9,0,11,0],[0,0,0,0]]

C++ 代码(逐行注释,和Python逻辑完全对应)

cpp 复制代码
#include <vector>
#include <iostream>
using namespace std;

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        // 1. 获取矩阵的行数m和列数n
        int m = matrix.size();  // 行数m
        int n = matrix[0].size();  // 列数n
        
        // 2. 临时变量col0,标记第一列是否需要置零(解决matrix[0][0]的冲突)
        // 初始值1:不需要置零;0:需要置零
        int col0 = 1;
        
        // 3. 第一步:遍历矩阵,用第一行、第一列和col0做标记(核心步骤)
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    // 3.1 标记第i行:将第i行第一个元素置为0
                    matrix[i][0] = 0;
                    
                    // 3.2 标记第j列:分情况处理,避免matrix[0][0]冲突
                    if (j == 0) {
                        // j=0(第一列),用col0标记,不修改matrix[0][0]
                        col0 = 0;
                    } else {
                        // j≠0,用第一行第j列元素标记第j列
                        matrix[0][j] = 0;
                    }
                }
            }
        }
        
        // 4. 第二步:置零(从第二行第二列开始,避免覆盖标记)
        // 跳过第一行和第一列,防止标记被破坏
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                // 只要当前行或当前列被标记(对应位置为0),就置零当前元素
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }
        
        // 5. 第三步:单独处理第一行(根据matrix[0][0]的标记)
        if (matrix[0][0] == 0) {
            for (int j = 0; j < n; j++) {
                matrix[0][j] = 0;  // 第一行所有元素置零
            }
        }
        
        // 6. 第四步:单独处理第一列(根据col0的标记)
        if (col0 == 0) {
            for (int i = 0; i < m; i++) {
                matrix[i][0] = 0;  // 第一列所有元素置零
            }
        }
    }
};

// 主函数(测试代码,和解法一、二一致,便于对比)
int main() {
    Solution solution;
    
    // 测试示例1
    cout << "=== 测试示例1 ===" << endl;
    vector<vector<int>> matrix1 = {{1,1,1},{1,0,1},{1,1,1}};
    cout << "输入矩阵:" << endl;
    for (int i = 0; i < matrix1.size(); i++) {
        for (int j = 0; j < matrix1[0].size(); j++) {
            cout << matrix1[i][j] << " ";
        }
        cout << endl;
    }
    solution.setZeroes(matrix1);
    cout << "输出矩阵:" << endl;
    for (int i = 0; i < matrix1.size(); i++) {
        for (int j = 0; j < matrix1[0].size(); j++) {
            cout << matrix1[i][j] << " ";
        }
        cout << endl;
    }
    
    // 测试示例2
    cout << "\n=== 测试示例2 ===" << endl;
    vector<vector<int>> matrix2 = {{0,1,2,0},{3,4,5,2},{1,3,1,5}};
    cout << "输入矩阵:" << endl;
    for (int i = 0; i < matrix2.size(); i++) {
        for (int j = 0; j < matrix2[0].size(); j++) {
            cout << matrix2[i][j] << " ";
        }
        cout << endl;
    }
    solution.setZeroes(matrix2);
    cout << "输出矩阵:" << endl;
    for (int i = 0; i < matrix2.size(); i++) {
        for (int j = 0; j < matrix2[0].size(); j++) {
            cout << matrix2[i][j] << " ";
        }
        cout << endl;
    }
    
    // 自定义测试
    cout << "\n=== 自定义测试 ===" << endl;
    vector<vector<int>> matrix3 = {{1,2,3,4},{5,0,7,8},{9,10,11,12},{13,14,15,0}};
    cout << "输入矩阵:" << endl;
    for (int i = 0; i < matrix3.size(); i++) {
        for (int j = 0; j < matrix3[0].size(); j++) {
            cout << matrix3[i][j] << " ";
        }
        cout << endl;
    }
    solution.setZeroes(matrix3);
    cout << "输出矩阵:" << endl;
    for (int i = 0; i < matrix3.size(); i++) {
        for (int j = 0; j < matrix3[0].size(); j++) {
            cout << matrix3[i][j] << " ";
        }
        cout << endl;
    }
    
    return 0;
}

运行流程(跟着走,以示例1为例,清晰理解标记和置零顺序)

示例1输入:matrix = [[1,1,1],[1,0,1],[1,1,1]],m=3,n=3,初始col0=1

  1. 第一步:遍历矩阵,做标记(核心): i=0(第0行):j=0→1→2,matrix[0][j]都是1,不做任何标记;

  2. i=1(第1行): j=0:matrix[1][0] = 1 → 不操作;

  3. j=1:matrix[1][1] = 0 → 触发标记: 标记第1行:matrix[1][0] = 0(把第1行第一个元素改成0);

  4. 标记第1列:j≠0,所以matrix[0][1] = 0(把第一行第1列改成0);

  5. j=2:matrix[1][2] = 1 → 不操作;

  6. i=2(第2行):j=0→1→2,matrix[2][j]都是1,不做任何标记;

  7. 第二步:从第二行第二列(i=1,j=1)开始置零: i=1(第1行): j=1:matrix[1][0] = 0(行标记为0)→ matrix[1][1] = 0(已为0,不变);

  8. j=2:matrix[1][0] = 0(行标记为0)→ matrix[1][2] = 0;

  9. i=2(第2行): j=1:matrix[0][1] = 0(列标记为0)→ matrix[2][1] = 0;

  10. j=2:matrix[2][0] = 1(行标记不为0)、matrix[0][2] = 1(列标记不为0)→ 不操作;

  11. 第三步:单独处理第一行:matrix[0][0] = 1(未标记)→ 第一行不置零(已符合要求);

  12. 第四步:单独处理第一列:col0=1(未标记)→ 第一列不置零(已符合要求);

  13. 最终结果:[[1,0,1],[0,0,0],[1,0,1]],和示例一致。

优缺点

  • ✅ 优点:空间复杂度O(1)(仅用1个临时变量col0),是题目要求的最优解;

  • ✅ 面试高频考点,必须掌握(面试官常问"如何用常量空间实现");

  • ✅ 不浪费额外内存,适合大规模矩阵(比如1000x1000矩阵,比解法一、二节省大量内存);

  • ❌ 缺点:逻辑比前两种解法稍复杂,需要注意"标记顺序"和"单独处理第一行、第一列",容易遗漏细节(比如忘记处理col0,导致第一列置零错误)。

四、全文总结

这道题的核心是"避免边遍历边置零",核心思路是"先标记,后置零",三种解法从易到难,适配不同场景,可按以下顺序学习、掌握:

1. 学习顺序

  1. 先学「解法一(暴力解法)」:不用考虑优化,重点理解"为什么不能边遍历边置零",掌握"复制矩阵→遍历标记→修改原矩阵"的思路,打好基础;

  2. 再学「解法二(优化解法)」:重点理解"标记数组"的作用,学会用O(m+n)空间优化,掌握"标记行和列→统一置零"的核心逻辑,这是面试中最基础、最常考的写法;

  3. 最后学「解法三(最优解法)」:重点掌握"利用矩阵自身空间做标记",记住"col0临时变量"的作用和"先置零非首行首列→单独处理首行首列"的顺序,应对面试进阶提问。

2. 三种解法对比

解法 空间复杂度 核心思路 适用场景
解法一(暴力) O(mn) 复制矩阵,遍历复制矩阵,修改原矩阵 入门、理解题意,实际开发/面试不推荐
解法二(优化) O(m+n) 用两个标记数组,标记行和列,统一置零 日常开发、基础面试,逻辑简单、不易出错
解法三(最优) O(1) 用矩阵首行首列+col0标记,单独处理首行首列 面试进阶、大规模矩阵,节省内存

3. 易错点总结(避坑)

  • 坑点1:边遍历边置零 → 解决办法:先标记所有需要置零的行和列,再统一置零;

  • 坑点2:解法三中,忘记用col0标记第一列 → 解决办法:记住matrix[0][0]不能同时标记首行和首列,用col0单独标记首列;

  • 坑点3:解法三中,置零顺序错误(先置零首行首列)→ 解决办法:先置零"第二行第二列及以后",再单独处理首行和首列;

  • 坑点4:C++中忘记用引用(&)→ 解决办法:函数参数必须写vector<vector<int>>& matrix,否则修改的是副本,原矩阵不变;

  • 坑点5:Python中复制矩阵用temp = matrix(浅复制)→ 解决办法:用temp = [row[:] for row in matrix],实现深复制,避免修改temp影响原矩阵。

相关推荐
youngerwang2 小时前
【矩阵不是数表,而是结构的身体】
线性代数·矩阵
阿Y加油吧2 小时前
二刷 LeetCode:两道经典贪心题复盘
算法·leetcode·职场和发展
Java成神之路-2 小时前
【LeetCode 刷题笔记】35. 搜索插入位置 | 二分查找经典入门题
算法·leetcode
SunnyByte2 小时前
数据结构——算法效率
数据结构
Navigator_Z12 小时前
LeetCode //C - 1033. Moving Stones Until Consecutive
c语言·算法·leetcode
jiushiapwojdap15 小时前
LU分解法求解线性方程组Matlab实现
数据结构·其他·算法·matlab
纽扣66716 小时前
【算法进阶之路】链表进阶:删除、合并、回文与排序全解析
数据结构·算法·链表
米饭不加菜17 小时前
机器人矩阵运算MATLAB计算
matlab·矩阵·机器人
xvhao201318 小时前
单源、多源最短路
数据结构·c++·算法·深度优先·动态规划·图论·图搜索算法