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

【LeetCode 热题 100】(六)矩阵

73. 矩阵置零

class Solution {public void setZeroes(int[][] matrix) {int row_length = matrix.length;int col_length = matrix[0].length;boolean[] row = new boolean[row_length];boolean[] col = new boolean[col_length];for (int i = 0; i < row_length; i++) {for (int j = 0; j < col_length; j++) {if(matrix[i][j] == 0){row[i] = true;col[j] = true;}}}for (int i = 0; i < row_length; i++) {for (int j = 0; j < col_length; j++) {if(row[i]== true || col[j] == true){matrix[i][j] = 0;}}}}
}       

解题思路描述

这段代码实现了 “矩阵置零” 问题。给定一个 m x n 矩阵,如果某个元素为 0,则将其所在行和列的所有元素都设为 0。代码需要满足原地修改的要求(不返回新矩阵)。

核心思想:标记法

使用两个标记数组分别记录哪些行和列需要置零:

  1. 行标记数组 row[]:记录包含 0 的行
  2. 列标记数组 col[]:记录包含 0 的列
    通过两次遍历完成操作:第一次扫描标记,第二次根据标记置零。
步骤详解
  1. 初始化标记数组

    • row[length]:布尔数组,长度 = 矩阵行数
    • col[length]:布尔数组,长度 = 矩阵列数
    • 初始值均为 false,表示暂无需要置零的行列
  2. 第一次遍历:标记需要置零的行列(时间复杂度 O(m*n))

    • 遍历每个元素 matrix[i][j]
    • 当元素值为 0 时:
      • 标记行:row[i] = true
      • 标记列:col[j] = true
    • 关键点:此时不修改矩阵值,避免影响后续标记
  3. 第二次遍历:执行置零操作(时间复杂度 O(m*n))

    • 再次遍历每个元素 matrix[i][j]
    • 若当前行被标记 (row[i] == true) 或当前列被标记 (col[j] == true)
    • 将元素置零:matrix[i][j] = 0
为什么有效?
  • 原地修改:直接操作输入矩阵,不返回新矩阵
  • 避免覆盖问题:先完成全部标记再修改,确保不会遗漏或误标记
  • 时间复杂度:O(m*n) 两次完整遍历矩阵
  • 空间复杂度:O(m+n) 使用两个额外标记数组
示例演算

以 3x3 矩阵为例:

初始矩阵:
[1, 2, 3]
[4, 0, 6]
[7, 8, 9]步骤1:标记行列在(1,1)发现0 → 标记 row[1]=true, col[1]=true步骤2:根据标记置零row[1]=true → 将第1行(索引1)全置零:[4,0,6]→[0,0,0]col[1]=true → 将第1列(索引1)全置零:[2]→0, [0]→0 (已置零), [8]→0最终结果:
[1, 0, 3]
[0, 0, 0]
[7, 0, 9]
优化方向

虽然当前代码清晰易懂,但空间复杂度可优化至 O(1):

  • 用矩阵第一行和第一列代替标记数组
  • 额外两个变量标记第一行/列是否需要置零
    但当前版本更易理解,适合展示核心思路。

54. 螺旋矩阵

class Solution {public List<Integer> spiralOrder(int[][] matrix) {List<Integer> order = new ArrayList<Integer>();if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return order;}int rows = matrix.length;int cols = matrix[0].length;// 四个角int left = 0, right = cols - 1, top = 0, bottom = rows - 1;while (left <= right && top <= bottom){// 1.从左向右for (int column = left; column <= right; column++) {order.add(matrix[top][column]);}// 2.从上往下for (int row = top+1; row <= bottom; row++){order.add(matrix[row][right]);}if(left < right && top< bottom){// 3.从右往左for (int column = right-1; column > left; column--) {order.add(matrix[bottom][column]);}// 4.从下往上for (int row = bottom; row > top; row--){order.add(matrix[row][left]);}}left++;right--;top++;bottom--;}return order;}
}

解题思路描述

这段代码实现了 “螺旋矩阵” 问题。给定一个 m x n 的矩阵,要求按照顺时针螺旋顺序返回所有元素。代码的核心思想是模拟螺旋遍历过程,通过控制边界完成遍历。

核心思想:边界收缩法

使用四个变量标记当前遍历的边界:

  • left:左边界列索引
  • right:右边界列索引
  • top:上边界行索引
  • bottom:下边界行索引
    每完成一圈螺旋遍历,就向内收缩边界,直到遍历完所有元素。
步骤详解
  1. 初始化边界

    • left = 0(最左列)
    • right = cols - 1(最右列)
    • top = 0(最上行)
    • bottom = rows - 1(最下行)
  2. 螺旋遍历(循环条件:left <= right && top <= bottom

    • 从左到右(上边界):
      • 遍历 top 行,从 leftright
      • 示例:3x3 矩阵中,遍历第0行的 [0][0], [0][1], [0][2]
    • 从上到下(右边界):
      • 遍历 right 列,从 top+1bottom
      • 示例:3x3 矩阵中,遍历第2列的 [1][2], [2][2]
    • 从右到左(下边界,需满足内圈条件):
      • 仅当 left < right && top < bottom 时执行(防止单行/单列重复遍历)
      • 遍历 bottom 行,从 right-1left+1
      • 示例:3x3 矩阵中,遍历第2行的 [2][1]
    • 从下到上(左边界,需满足内圈条件):
      • 遍历 left 列,从 bottomtop+1
      • 示例:3x3 矩阵中,遍历第0列的 [2][0], [1][0]
  3. 边界收缩

    • 每完成一圈:left++, right--, top++, bottom--
    • 示例:3x3 矩阵第一圈后,边界变为 left=1, right=1, top=1, bottom=1
  4. 内圈处理

    • 当边界收缩后仍满足 left <= right && top <= bottom 时继续遍历
    • 示例:3x3 矩阵内圈只剩 [1][1] 元素
关键点解析
  1. 内圈条件判断

    • 从右到左和从下到上的遍历需要满足 left < right && top < bottom
    • 避免单行/单列时的重复遍历(如 3x1 矩阵)
  2. 边界处理顺序

    • 固定顺序:左→右、上→下、右→左、下→上
    • 每次只处理当前边界的最外层
  3. 终止条件

    • 当左边界越过右边界或上边界越过下边界时结束
示例演算(3x3 矩阵)
初始矩阵:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]遍历过程:
1. 左→右: [0][0]=1, [0][1]=2, [0][2]=3
2. 上→下: [1][2]=6, [2][2]=9
3. 右→左(内圈): [2][1]=8
4. 下→上(内圈): [2][0]=7, [1][0]=4
5. 边界收缩:left=1, right=1, top=1, bottom=1
6. 左→右(内圈): [1][1]=5结果:[1,2,3,6,9,8,7,4,5]
复杂度分析
  • 时间复杂度:O(m×n)
    每个元素恰好被访问一次
  • 空间复杂度:O(1)(不计输出列表)
    仅使用固定数量的边界变量
特殊矩阵处理
  • 单行矩阵:仅执行左→右遍历
  • 单列矩阵:执行左→右和上→下遍历
  • 空矩阵:开始时的空值检查直接返回空列表

这个解法通过精确控制边界移动和遍历方向,高效地实现了螺旋顺序遍历,是处理此类问题的经典方法。

48. 旋转图像

class Solution {public void rotate(int[][] matrix) {int n = matrix.length;int[][] matrix_new = new int[n][n];for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {matrix_new[j][n-1-i] = matrix[i][j];}}for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {matrix[i][j] = matrix_new[i][j];}}}
}

解题思路描述:旋转图像(顺时针90度)

方法1:使用额外空间(O(n²)空间复杂度)

核心思路:创建一个新矩阵,按照旋转规律映射元素位置,最后复制回原矩阵。

解题步骤

  1. 创建新矩阵

    int n = matrix.length;
    int[][] matrix_new = new int[n][n]; // 创建与输入矩阵相同大小的新矩阵
    
  2. 元素位置映射

    • 旋转规律:原矩阵中 [i][j] 位置的元素,在旋转后应位于 [j][n-1-i]
    • 映射实现
      for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {matrix_new[j][n-1-i] = matrix[i][j];}
      }
      
  3. 复制回原矩阵

    for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {matrix[i][j] = matrix_new[i][j];}
    }
    

关键点解析

  • 空间复杂度:O(n²)(需要额外n×n空间存储新矩阵)
  • 正确性保证:不修改原矩阵数据,避免覆盖问题
  • 时间复杂度:O(n²)(两次完整遍历)

示例演算(2×2矩阵):

原矩阵:       旋转后:
[1,2]         [3,1]
[3,4]  →→→→   [4,2]映射过程:
1→[0][0] → 新位置[0][1]
2→[0][1] → 新位置[1][1]
3→[1][0] → 新位置[0][0]
4→[1][1] → 新位置[1][0]
方法2:原地旋转(O(1)空间复杂度)

核心思路:通过两次翻转操作实现原地旋转,无需额外空间。

解题步骤

  1. 主对角线翻转(矩阵转置)

    • 交换元素位置:matrix[i][j]matrix[j][i]
    • 关键:只需遍历上三角区域,避免重复交换
  2. 水平翻转(逐行逆序)

    • 交换元素位置:matrix[i][j]matrix[i][n-1-j]
    • 关键:每行只需遍历前半部分

完整代码实现

public void rotate(int[][] matrix) {int n = matrix.length;// 1. 主对角线翻转(转置)for (int i = 0; i < n; i++) {for (int j = i; j < n; j++) { // 注意:j从i开始int temp = matrix[i][j];matrix[i][j] = matrix[j][i];matrix[j][i] = temp;}}// 2. 水平翻转(逐行逆序)for (int i = 0; i < n; i++) {for (int j = 0; j < n/2; j++) { // 只需遍历半行int temp = matrix[i][j];matrix[i][j] = matrix[i][n-1-j];matrix[i][n-1-j] = temp;}}
}

关键点解析

  1. 旋转的数学本质

    顺时针90° = 转置 + 水平翻转
    逆时针90° = 转置 + 垂直翻转
    
  2. 遍历范围优化

    • 转置时:只遍历上三角区(j从i开始),避免重复交换
    • 翻转时:每行遍历前半部分(j < n/2)
  3. 空间复杂度:O(1)(仅使用常数临时变量)

示例演算(3×3矩阵):

原矩阵:
[1,2,3]
[4,5,6]
[7,8,9]步骤1:转置(主对角线翻转):
[1,4,7]
[2,5,8]
[3,6,9]步骤2:水平翻转(逐行逆序):
[7,4,1]  ← 第一行:1和7交换,4不动
[8,5,2]  ← 第二行:2和8交换
[9,6,3]  ← 第三行:3和9交换(实际操作时按行翻转)最终结果(顺时针旋转90°):
[7,4,1]
[8,5,2]
[9,6,3]

两种方法对比

特性方法1(额外空间)方法2(原地旋转)
空间复杂度O(n²)O(1)
可读性直观易懂需要理解旋转数学原理
适用场景内存空间充足时内存受限环境
修改方式创建新矩阵直接修改输入矩阵
时间复杂度O(n²)(两次遍历)O(n²)(两次遍历)

方法2通过巧妙的两次翻转操作,在不使用额外空间的情况下高效实现了矩阵旋转,是解决此类问题的经典原地算法。

http://www.dtcms.com/a/322499.html

相关文章:

  • 解决本地连接服务器ollama的错误
  • 网站站长如何借助php推送示例提交网站内容加速百度收录?
  • 【26】C#实战篇—— 多个线程函数对同一个 Excel 文件进行写操作引起的文件冲突问题,解决方法
  • 代码随想录day60图论10
  • 使用 Ansys Discovery 进行动态设计和分析
  • Mac屏幕取色不准?探究原理和换算规则
  • Linux文件系统基石:透彻理解inode及其核心作用
  • LeetCode111~130题解
  • ABP VNext + Akka.NET:高并发处理与分布式计算
  • 【AGI】GPT-5:博士级AI助手的全面进化与协作智能时代的黎明
  • 如何输出一篇高质量的版本测试策略
  • WebGIS视角下基孔肯雅热流行风险地区分类实战解析
  • jupyter服务器创建账户加映射对外账户地址
  • stm32项目(24)——基于STM32的汽车CAN通信系统
  • React中实现完整的登录鉴权与权限控制系统
  • (一)React复习小满(userImmer/userMemo/useContext/userCallback/userRef)
  • 需求评审需要哪些角色参与
  • 嵌入式 - Linux软件编程
  • Web文件上传:本地与云存储实战
  • day 36_2025-08-09
  • 如何在 Ubuntu 24.04 LTS Linux 上安装 Azure Data Studio
  • C# 通过第三方库INIFileParser管理INI配置文件
  • Golang的本地缓存freecache
  • Linux中Docker redis介绍以及应用
  • Kubernetes(K8s)不同行业的典型应用场景及价值分析 原创
  • 【31】C#实战篇——获取路径下的文件名(不包含路径和扩展名),并分离出文件名`fileName` ,文件名编号`SN`,文件名前缀`WMT`
  • 功能测试中常见的面试题-二
  • kettle插件-kettle MinIO插件,轻松解决文件上传到MinIO服务器
  • Nginx高性能web服务器
  • 如何衡量需求的紧急程度