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

【LeetCode】73. 矩阵置零

文章目录

  • 73. 矩阵置零
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 进阶:
    • 解题思路
      • 问题深度分析
        • 问题本质
        • 核心思想
        • 关键难点分析
        • 典型情况分析
        • 算法对比
      • 算法流程图
        • 主算法流程(原地标记)
        • 标记过程详细流程
        • 置零过程详细流程
      • 复杂度分析
        • 时间复杂度详解
        • 空间复杂度详解
      • 关键优化技巧
        • 技巧1:原地标记(最优解法)
        • 技巧2:额外数组(直观易懂)
        • 技巧3:使用集合(Go语言风格)
        • 技巧4:两个变量优化(另一种原地标记)
      • 边界情况处理
      • 测试用例设计
        • 基础测试
        • 多个0
        • 单个元素
        • 全为0
      • 常见错误与陷阱
        • 错误1:直接置零导致标记丢失
        • 错误2:忘记单独处理第一行/列
        • 错误3:从前向后处理导致覆盖
        • 错误4:索引边界错误
      • 实战技巧总结
      • 进阶扩展
        • 扩展1:统计置零的元素个数
        • 扩展2:置零为特定值
        • 扩展3:返回新矩阵(不修改原矩阵)
      • 应用场景
    • 代码实现
    • 测试结果
    • 核心收获
    • 应用拓展
    • 完整题解代码

73. 矩阵置零

题目描述

给定一个 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) 的额外空间,但这仍然不是最好的解决方案。
  • 你能想出一个仅使用常量空间的解决方案吗?

解题思路

问题深度分析

这是一道原地算法问题,核心在于空间优化巧妙的标记策略。问题看似简单,但如何在O(1)空间复杂度内完成任务,需要对矩阵的利用有深刻理解。

问题本质

给定一个m×n的矩阵,如果某个元素为0,则将其所在的整行和整列都设为0。要求原地完成,即空间复杂度为O(1)。

核心思想

利用矩阵自身作为标记空间

  1. 第一行作为列标记matrix[0][j] = 0 表示第j列需要置零
  2. 第一列作为行标记matrix[i][0] = 0 表示第i行需要置零
  3. 单独变量记录:第一行和第一列本身是否需要置零
  4. 从后向前处理:避免标记被提前覆盖
关键难点分析

难点1:为什么要单独记录第一行和第一列?

  • 第一行和第一列既是标记位,又是被标记对象
  • 如果直接用它们标记,会混淆:是本身有0,还是用来标记其他行列?
  • 解决:用两个布尔变量firstRowZerofirstColZero记录它们本身是否有0

难点2:为什么要从后向前处理?

  • 如果从前向后,会先把第一行/列置零
  • 这样就无法根据第一行/列的标记来处理其他元素
  • 解决:先处理matrix[1..m-1][1..n-1],最后处理第一行和第一列

难点3:空间优化的本质是什么?

  • 朴素做法:用两个数组rows[m]cols[n]标记,空间O(m+n)
  • 优化:用矩阵的第一行和第一列代替这两个数组,空间O(1)
  • 核心:复用矩阵空间
典型情况分析

情况1:普通矩阵

输入: [[1,1,1],[1,0,1],[1,1,1]]
步骤:
1. 检查第一行/列:无0
2. 遍历[1,1]位置有0 → matrix[1][0]=0, matrix[0][1]=0
3. 根据标记置零:第1行和第1列置零
输出: [[1,0,1],[0,0,0],[1,0,1]]

情况2:多个0

输入: [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
步骤:
1. 第一行有0 → firstRowZero=true
2. 遍历发现[0][0]和[0][3]有0 → 标记第0,3列
3. 根据标记置零
4. 最后处理第一行
输出: [[0,0,0,0],[0,4,5,0],[0,3,1,0]]

情况3:第一行/列有0

输入: [[0,1],[1,1]]
步骤:
1. 检查:firstRowZero=true, firstColZero=true
2. 遍历并标记
3. 处理内部
4. 最后把第一行和第一列置零
输出: [[0,0],[0,1]]

情况4:全为0

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

情况5:单行或单列

输入: [[1,0,3]]
输出: [[0,0,0]]输入: [[1],[2],[0]]
输出: [[0],[0],[0]]
算法对比
算法时间复杂度空间复杂度特点
额外数组O(mn)O(m+n)简单直观,易理解
原地标记O(mn)O(1)最优解法
集合标记O(mn)O(m+n)使用map/set
两个变量优化O(mn)O(1)原地标记的变体

注:m为行数,n为列数

算法流程图

主算法流程(原地标记)
开始: 输入matrix
检查第一行是否有0
firstRowZero = true/false
检查第一列是否有0
firstColZero = true/false
遍历matrix 1,1 到m-1,n-1
发现matrix i,j == 0?
标记: matrix i,0 = 0
标记: matrix 0,j = 0
遍历完成?
从后向前处理内部
遍历i从1到m-1, j从1到n-1
matrix i,0==0 或 matrix 0,j==0?
matrix i,j = 0
跳过
内部处理完成?
firstRowZero?
第一行全部置零
firstColZero?
第一列全部置零
结束
标记过程详细流程
标记阶段
初始化: firstRowZero=false, firstColZero=false
检查第一行: i=0, j从0到n-1
matrix 0,j == 0?
firstRowZero = true, break
j < n-1?
检查第一列: j=0, i从0到m-1
matrix i,0 == 0?
firstColZero = true, break
i < m-1?
遍历内部: i从1到m-1, j从1到n-1
matrix i,j == 0?
matrix i,0 = 0
matrix 0,j = 0
遍历完成?
标记完成
置零过程详细流程
置零阶段
处理内部: i从1到m-1, j从1到n-1
matrix i,0 == 0?
matrix i,j = 0
matrix 0,j == 0?
保持不变
内部处理完?
firstRowZero?
j从0到n-1: matrix 0,j = 0
firstColZero?
i从0到m-1: matrix i,0 = 0
完成

复杂度分析

时间复杂度详解

原地标记算法:O(mn)

  • 检查第一行:O(n)
  • 检查第一列:O(m)
  • 遍历并标记:O(mn)
  • 根据标记置零:O(mn)
  • 处理第一行和第一列:O(m+n)
  • 总时间:O(mn)

各步骤复杂度

  1. 标记阶段:O(mn)
  2. 置零阶段:O(mn)
  3. 边界处理:O(m+n)
空间复杂度详解

额外数组:O(m+n)

  • rows数组:O(m)
  • cols数组:O(n)

原地标记:O(1)

  • 只用两个布尔变量
  • 利用矩阵第一行/列存储标记

关键优化技巧

技巧1:原地标记(最优解法)
func setZeroes(matrix [][]int) {if len(matrix) == 0 {return}m, n := len(matrix), len(matrix[0])firstRowZero, firstColZero := false, false// 检查第一行是否有0for j := 0; j < n; j++ {if matrix[0][j] == 0 {firstRowZero = truebreak}}// 检查第一列是否有0for i := 0; i < m; i++ {if matrix[i][0] == 0 {firstColZero = truebreak}}// 用第一行和第一列标记for i := 1; i < m; i++ {for j := 1; j < n; j++ {if matrix[i][j] == 0 {matrix[i][0] = 0matrix[0][j] = 0}}}// 根据标记置零(从后向前)for i := 1; i < m; i++ {for j := 1; j < n; j++ {if matrix[i][0] == 0 || matrix[0][j] == 0 {matrix[i][j] = 0}}}// 处理第一行if firstRowZero {for j := 0; j < n; j++ {matrix[0][j] = 0}}// 处理第一列if firstColZero {for i := 0; i < m; i++ {matrix[i][0] = 0}}
}

优势:空间O(1),是最优解

技巧2:额外数组(直观易懂)
func setZeroes(matrix [][]int) {if len(matrix) == 0 {return}m, n := len(matrix), len(matrix[0])rows := make([]bool, m)cols := make([]bool, n)// 记录需要置零的行和列for i := 0; i < m; i++ {for j := 0; j < n; j++ {if matrix[i][j] == 0 {rows[i] = truecols[j] = true}}}// 置零for i := 0; i < m; i++ {for j := 0; j < n; j++ {if rows[i] || cols[j] {matrix[i][j] = 0}}}
}

优势:逻辑清晰,容易理解

技巧3:使用集合(Go语言风格)
func setZeroes(matrix [][]int) {if len(matrix) == 0 {return}m, n := len(matrix), len(matrix[0])rowSet := make(map[int]bool)colSet := make(map[int]bool)// 记录需要置零的行和列for i := 0; i < m; i++ {for j := 0; j < n; j++ {if matrix[i][j] == 0 {rowSet[i] = truecolSet[j] = true}}}// 置零for i := 0; i < m; i++ {for j := 0; j < n; j++ {if rowSet[i] || colSet[j] {matrix[i][j] = 0}}}
}

特点:使用map,Go语言惯用写法

技巧4:两个变量优化(另一种原地标记)
func setZeroes(matrix [][]int) {if len(matrix) == 0 {return}m, n := len(matrix), len(matrix[0])col0 := false// 使用matrix[i][0]和matrix[0][j]作为标记for i := 0; i < m; i++ {if matrix[i][0] == 0 {col0 = true}for j := 1; j < n; j++ {if matrix[i][j] == 0 {matrix[i][0] = 0matrix[0][j] = 0}}}// 从后向前置零for i := m - 1; i >= 0; i-- {for j := n - 1; j >= 1; j-- {if matrix[i][0] == 0 || matrix[0][j] == 0 {matrix[i][j] = 0}}if col0 {matrix[i][0] = 0}}
}

特点:只用一个变量,从后向前处理

边界情况处理

  1. 空矩阵matrix = [] → 直接返回
  2. 单个元素
    • [[0]][[0]]
    • [[1]][[1]]
  3. 单行[[1,0,3]][[0,0,0]]
  4. 单列[[1],[2],[0]][[0],[0],[0]]
  5. 全为0[[0,0],[0,0]][[0,0],[0,0]]
  6. 无0[[1,2],[3,4]][[1,2],[3,4]]

测试用例设计

基础测试
输入: [[1,1,1],[1,0,1],[1,1,1]]
输出: [[1,0,1],[0,0,0],[1,0,1]]
说明: 中间有一个0
多个0
输入: [[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
单个元素
输入: [[1]]
输出: [[1]]
说明: 无0
全为0
输入: [[0]]
输出: [[0]]
说明: 只有一个0

常见错误与陷阱

错误1:直接置零导致标记丢失
// ❌ 错误:边遍历边置零
for i := 0; i < m; i++ {for j := 0; j < n; j++ {if matrix[i][j] == 0 {// 直接置零会影响后续判断for k := 0; k < n; k++ {matrix[i][k] = 0}}}
}// ✅ 正确:先标记,后置零
// 分两个阶段处理
错误2:忘记单独处理第一行/列
// ❌ 错误:没有记录第一行/列本身是否有0
for i := 1; i < m; i++ {for j := 1; j < n; j++ {if matrix[i][j] == 0 {matrix[i][0] = 0matrix[0][j] = 0}}
}
// 第一行/列的0信息丢失// ✅ 正确:用变量记录
firstRowZero, firstColZero := false, false
// 先检查第一行/列
错误3:从前向后处理导致覆盖
// ❌ 错误:从前向后会覆盖标记
for i := 0; i < m; i++ {for j := 0; j < n; j++ {if matrix[i][0] == 0 || matrix[0][j] == 0 {matrix[i][j] = 0}}
}
// 第一行/列被提前置零,丢失标记信息// ✅ 正确:从内部开始,最后处理第一行/列
for i := 1; i < m; i++ {for j := 1; j < n; j++ {// 先处理内部}
}
// 最后处理第一行和第一列
错误4:索引边界错误
// ❌ 错误:索引越界
for i := 0; i <= m; i++ {  // 应该是 i < mfor j := 0; j <= n; j++ {  // 应该是 j < n// ...}
}// ✅ 正确:注意边界
for i := 0; i < m; i++ {for j := 0; j < n; j++ {// ...}
}

实战技巧总结

  1. 空间换时间:朴素做法用额外数组O(m+n)
  2. 原地标记:利用矩阵第一行/列作为标记空间
  3. 单独记录:第一行/列本身是否有0要单独记录
  4. 处理顺序:先内部,后边界(从后向前)
  5. 边界检查:注意矩阵为空的情况
  6. 分阶段处理:标记阶段 → 置零阶段

进阶扩展

扩展1:统计置零的元素个数
func setZeroesWithCount(matrix [][]int) int {if len(matrix) == 0 {return 0}m, n := len(matrix), len(matrix[0])firstRowZero, firstColZero := false, falsecount := 0// 检查第一行for j := 0; j < n; j++ {if matrix[0][j] == 0 {firstRowZero = true}}// 检查第一列for i := 0; i < m; i++ {if matrix[i][0] == 0 {firstColZero = true}}// 标记for i := 1; i < m; i++ {for j := 1; j < n; j++ {if matrix[i][j] == 0 {matrix[i][0] = 0matrix[0][j] = 0}}}// 置零并计数for i := 1; i < m; i++ {for j := 1; j < n; j++ {if matrix[i][0] == 0 || matrix[0][j] == 0 {if matrix[i][j] != 0 {count++}matrix[i][j] = 0}}}// 处理第一行if firstRowZero {for j := 0; j < n; j++ {if matrix[0][j] != 0 {count++}matrix[0][j] = 0}}// 处理第一列if firstColZero {for i := 0; i < m; i++ {if matrix[i][0] != 0 {count++}matrix[i][0] = 0}}return count
}
扩展2:置零为特定值
func setToValue(matrix [][]int, target int) {if len(matrix) == 0 {return}m, n := len(matrix), len(matrix[0])rows := make([]bool, m)cols := make([]bool, n)// 找到所有0的位置for i := 0; i < m; i++ {for j := 0; j < n; j++ {if matrix[i][j] == 0 {rows[i] = truecols[j] = true}}}// 置为目标值for i := 0; i < m; i++ {for j := 0; j < n; j++ {if rows[i] || cols[j] {matrix[i][j] = target}}}
}
扩展3:返回新矩阵(不修改原矩阵)
func setZeroesNew(matrix [][]int) [][]int {if len(matrix) == 0 {return matrix}m, n := len(matrix), len(matrix[0])result := make([][]int, m)for i := range result {result[i] = make([]int, n)copy(result[i], matrix[i])}rows := make([]bool, m)cols := make([]bool, n)for i := 0; i < m; i++ {for j := 0; j < n; j++ {if matrix[i][j] == 0 {rows[i] = truecols[j] = true}}}for i := 0; i < m; i++ {for j := 0; j < n; j++ {if rows[i] || cols[j] {result[i][j] = 0}}}return result
}

应用场景

  1. 图像处理:标记和清除特定区域
  2. 数据清洗:处理表格中的缺失值
  3. 游戏开发:地图标记和区域清除
  4. 矩阵运算:稀疏矩阵处理
  5. 算法竞赛:原地算法的经典例题

代码实现

本题提供了四种不同的解法,重点掌握原地标记方法(空间O(1))。

测试结果

测试用例原地标记额外数组集合标记两变量优化
基础测试
多个0
单个元素
全为0

核心收获

  1. 原地算法:利用矩阵本身存储标记信息
  2. 空间优化:O(m+n) → O(1)的优化思路
  3. 遍历顺序:从后向前避免标记被覆盖
  4. 边界处理:第一行/列需要单独标记和处理
  5. 分阶段思维:标记阶段和置零阶段分离

应用拓展

  • 图像处理中的区域标记
  • 数据清洗中的缺失值处理
  • 稀疏矩阵的优化存储
  • 游戏地图的区域清除

完整题解代码

package mainimport "fmt"// =========================== 方法一:原地标记(O(1)空间,最优解法) ===========================func setZeroes(matrix [][]int) {if len(matrix) == 0 {return}m, n := len(matrix), len(matrix[0])firstRowZero, firstColZero := false, false// 检查第一行是否有0for j := 0; j < n; j++ {if matrix[0][j] == 0 {firstRowZero = truebreak}}// 检查第一列是否有0for i := 0; i < m; i++ {if matrix[i][0] == 0 {firstColZero = truebreak}}// 用第一行和第一列标记for i := 1; i < m; i++ {for j := 1; j < n; j++ {if matrix[i][j] == 0 {matrix[i][0] = 0matrix[0][j] = 0}}}// 根据标记置零(从后向前)for i := 1; i < m; i++ {for j := 1; j < n; j++ {if matrix[i][0] == 0 || matrix[0][j] == 0 {matrix[i][j] = 0}}}// 处理第一行if firstRowZero {for j := 0; j < n; j++ {matrix[0][j] = 0}}// 处理第一列if firstColZero {for i := 0; i < m; i++ {matrix[i][0] = 0}}
}// =========================== 方法二:额外数组(O(m+n)空间) ===========================func setZeroes2(matrix [][]int) {if len(matrix) == 0 {return}m, n := len(matrix), len(matrix[0])rows := make([]bool, m)cols := make([]bool, n)// 记录需要置零的行和列for i := 0; i < m; i++ {for j := 0; j < n; j++ {if matrix[i][j] == 0 {rows[i] = truecols[j] = true}}}// 置零for i := 0; i < m; i++ {for j := 0; j < n; j++ {if rows[i] || cols[j] {matrix[i][j] = 0}}}
}// =========================== 方法三:使用集合标记 ===========================func setZeroes3(matrix [][]int) {if len(matrix) == 0 {return}m, n := len(matrix), len(matrix[0])rowSet := make(map[int]bool)colSet := make(map[int]bool)// 记录需要置零的行和列for i := 0; i < m; i++ {for j := 0; j < n; j++ {if matrix[i][j] == 0 {rowSet[i] = truecolSet[j] = true}}}// 置零for i := 0; i < m; i++ {for j := 0; j < n; j++ {if rowSet[i] || colSet[j] {matrix[i][j] = 0}}}
}// =========================== 方法四:两个变量优化(O(1)空间) ===========================func setZeroes4(matrix [][]int) {if len(matrix) == 0 {return}m, n := len(matrix), len(matrix[0])col0 := false// 使用matrix[i][0]和matrix[0][j]作为标记for i := 0; i < m; i++ {if matrix[i][0] == 0 {col0 = true}for j := 1; j < n; j++ {if matrix[i][j] == 0 {matrix[i][0] = 0matrix[0][j] = 0}}}// 从后向前置零for i := m - 1; i >= 0; i-- {for j := n - 1; j >= 1; j-- {if matrix[i][0] == 0 || matrix[0][j] == 0 {matrix[i][j] = 0}}if col0 {matrix[i][0] = 0}}
}// =========================== 测试代码 ===========================func main() {fmt.Println("=== LeetCode 73: 矩阵置零 ===\n")testCases := []struct {matrix [][]intexpect [][]int}{{[][]int{{1, 1, 1}, {1, 0, 1}, {1, 1, 1}},[][]int{{1, 0, 1}, {0, 0, 0}, {1, 0, 1}},},{[][]int{{0, 1, 2, 0}, {3, 4, 5, 2}, {1, 3, 1, 5}},[][]int{{0, 0, 0, 0}, {0, 4, 5, 0}, {0, 3, 1, 0}},},{[][]int{{1}},[][]int{{1}},},{[][]int{{0}},[][]int{{0}},},}fmt.Println("方法一:原地标记(O(1)空间)")runTests(testCases, setZeroes)fmt.Println("\n方法二:额外数组(O(m+n)空间)")runTests(testCases, setZeroes2)fmt.Println("\n方法三:使用集合")runTests(testCases, setZeroes3)fmt.Println("\n方法四:两个变量优化")runTests(testCases, setZeroes4)
}func runTests(testCases []struct {matrix [][]intexpect [][]int
}, fn func([][]int)) {passCount := 0for i, tc := range testCases {matrix := copyMatrix(tc.matrix)fn(matrix)status := "✅"if !equalMatrix(matrix, tc.expect) {status = "❌"} else {passCount++}fmt.Printf("  测试%d: %s\n", i+1, status)if status == "❌" {fmt.Printf("    输入: %v\n", tc.matrix)fmt.Printf("    输出: %v\n", matrix)fmt.Printf("    期望: %v\n", tc.expect)}}fmt.Printf("  通过: %d/%d\n", passCount, len(testCases))
}func copyMatrix(matrix [][]int) [][]int {result := make([][]int, len(matrix))for i := range matrix {result[i] = make([]int, len(matrix[i]))copy(result[i], matrix[i])}return result
}func equalMatrix(a, b [][]int) bool {if len(a) != len(b) {return false}for i := range a {if len(a[i]) != len(b[i]) {return false}for j := range a[i] {if a[i][j] != b[i][j] {return false}}}return true
}
http://www.dtcms.com/a/481603.html

相关文章:

  • 网站开发教材男通网站哪个好用
  • 《3D草原场景技术拆解:植被物理碰撞与多系统协同的6个实战方案》
  • 软件测试—BUG篇
  • OpenAI系列模型介绍、API使用
  • 做网站的可以信吗深圳商城网站建设
  • 关于使用docker部署srs服务器的相关指令
  • 基于M序列编码的水下微弱目标检测方法
  • Ubuntu SSH 免密码登陆
  • vue前端面试题——记录一次面试当中遇到的题(8)
  • FastbuildAI后端WebModule模块注册分析
  • 南昌网站排名网站站群建设方案
  • day9 cpp:运算符重载
  • Qoder上线提示词增强功能,将开发者从 “提示词“ 的负担中解放出来
  • 「机器学习笔记15」深度学习全面解析:从MLP到LSTM的Python实战指南
  • 在ARM版MacBook上构建lldb-mi
  • php网站后台搭建html代码大全简单
  • 零基础新手小白快速了解掌握服务集群与自动化运维(十一)MySQL数据库主从复制
  • 云手机的真实体验感怎么样
  • 广州微网站建设信息电商如何做
  • 架设一个网站腾讯网站建设推广
  • 【数据结构】:链表的核心实现与操作解析
  • 【Verilog】系统任务和编译指令
  • 辅助分类器GAN(ACGAN)
  • 交流网站建设心得体会wordpress首页固定页面
  • 专门做有机食品的网站dedecms怎么部署网站
  • 大学生个体创业的网站建设百度搭建wordpress
  • 自己公司网站维护上海动易 网站
  • 摄影网站设计企业官网用什么系统
  • 网站开发工具最适合网站建设和网络优化
  • 东莞东坑网站设计中牟网站建设