【力扣-73. 矩阵置零 ✨】Python笔记

5 阅读4分钟

【算法精讲】LeetCode 73. 矩阵置零:原地标记的巧妙艺术

摘要:本文详解 LeetCode 73 题“矩阵置零”,在严格限制原地修改的前提下,利用矩阵首行首列作为标记位,避免额外空间开销。通过两遍遍历完成行列清零,时间 O(MN),空间 O(1),是空间优化思维的经典范例。


🧠 核心知识点:什么是“原地算法”?

在算法题中,“原地(In-place)”通常指:

  • 不使用与输入规模成比例的额外空间
  • 允许使用常数个变量(如 i, j, flag 等)。
  • 可以直接修改输入数据结构本身。

对于二维矩阵问题,若要求“原地”,我们常需思考:

能否复用矩阵自身的某部分空间来存储中间状态?

本题正是这一思想的绝佳实践 —— 我们用第一行和第一列作为“标记区”,记录哪些行/列需要被置零。


🎯 题目引入:LeetCode 73. 矩阵置零

题目描述

给定一个 m x n 的矩阵 matrix,如果某个元素为 0,则将其所在整行整列的所有元素都设为 0。请原地完成此操作。

约束条件:

  • 不能使用额外数组记录哪些行/列要清零。
  • 必须修改原矩阵,且空间复杂度为 O(1)O(1)

示例 1:

输入: [[1,1,1],
       [1,0,1],
       [1,1,1]]
输出: [[1,0,1],
       [0,0,0],
       [1,0,1]]

示例 2:

输入: [[0,1,2,0],
       [3,4,5,2],
       [1,3,1,5]]
输出: [[0,0,0,0],
       [0,4,5,0],
       [0,3,1,0]]

💡 解题思路详解

❗ 为什么不能边遍历边置零?

如果你遇到一个 0 就立刻把它的行和列全改成 0,那么后续遍历时会误判这些新生成的 0 是原始数据,导致错误扩散!

✅ 正确做法:先标记,后执行

🔍 关键洞察:用第一行和第一列当“记事本”

我们可以这样做:

  1. 第一步:检查第一行和第一列是否原本就有 0

    • 因为我们要借用它们做标记,所以得提前记下它们自己是否需要被清零。
    • 用两个布尔变量 first_row_has_zerofirst_col_has_zero 记录。
  2. 第二步:用第一行和第一列标记其他行列

    • 遍历矩阵内部(从 (1,1) 开始),如果发现 matrix[i][j] == 0,就把 matrix[i][0]matrix[0][j] 设为 0,表示第 i 行和第 j 列需要清零。
  3. 第三步:根据标记清零内部区域

    • 再次遍历内部区域,如果 matrix[i][0] == 0matrix[0][j] == 0,就把 matrix[i][j] 设为 0
  4. 第四步:处理第一行和第一列本身

    • 如果第一步中记录过第一行/列有零,则将它们整个清零。

💻 代码实现与逐行解析

from typing import List

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        if not matrix or not matrix[0]:
            return
        
        m, n = len(matrix), len(matrix[0])
        
        # Step 1: 检查第一行和第一列是否有0
        first_row_has_zero = any(matrix[0][j] == 0 for j in range(n))
        first_col_has_zero = any(matrix[i][0] == 0 for i in range(m))
        
        # Step 2: 用第一行和第一列标记其他行列
        for i in range(1, m):
            for j in range(1, n):
                if matrix[i][j] == 0:
                    matrix[i][0] = 0      # 标记第i行
                    matrix[0][j] = 0      # 标记第j列
        
        # Step 3: 根据标记清零内部区域
        for i in range(1, m):
            for j in range(1, n):
                if matrix[i][0] == 0 or matrix[0][j] == 0:
                    matrix[i][j] = 0
        
        # Step 4: 处理第一行和第一列本身
        if first_row_has_zero:
            for j in range(n):
                matrix[0][j] = 0
        
        if first_col_has_zero:
            for i in range(m):
                matrix[i][0] = 0

⚠️ 易错点提醒

  1. 忘记单独处理第一行/列:因为它们既是“标记区”又是“待清零区”,必须提前记录其初始状态。
  2. 边界情况未考虑:空矩阵、单行/单列矩阵需特殊处理(虽然本题测试用例一般保证非空)。
  3. 标记冲突:确保只在内部区域进行标记,避免覆盖首行首列的原始信息。

🔄 对比其他解法

方法时间复杂度空间复杂度是否原地
暴力法O(MN)O(M+N)
使用集合记录O(MN)O(M+N)
本题解法O(MN)O(1)

✅ 本题解法是空间最优解,也是面试高频考点!