【LeetCode 热题 100】(六)矩阵

73. 矩阵置零

java 复制代码
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. 螺旋矩阵

java 复制代码
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. 旋转图像

java 复制代码
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. 创建新矩阵

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

    • 旋转规律 :原矩阵中 [i][j] 位置的元素,在旋转后应位于 [j][n-1-i]

    • 映射实现

      java 复制代码
      for (int i = 0; i < n; ++i) {
          for (int j = 0; j < n; ++j) {
              matrix_new[j][n-1-i] = matrix[i][j];
          }
      }
  3. 复制回原矩阵

    java 复制代码
    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]
    • 关键:每行只需遍历前半部分

完整代码实现

java 复制代码
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通过巧妙的两次翻转操作,在不使用额外空间的情况下高效实现了矩阵旋转,是解决此类问题的经典原地算法。

相关推荐
天地一流殇3 小时前
SimBA算法实现过程
深度学习·算法·对抗攻击·黑盒
2501_924730614 小时前
智慧城管复杂人流场景下识别准确率↑32%:陌讯多模态感知引擎实战解析
大数据·人工智能·算法·计算机视觉·目标跟踪·视觉检测·边缘计算
weixin_307779134 小时前
C++实现MATLAB矩阵计算程序
开发语言·c++·算法·matlab·矩阵
学不动CV了4 小时前
FreeRTOS入门知识(初识RTOS任务调度)(三)
c语言·arm开发·stm32·单片机·物联网·算法·51单片机
Kingfar_14 小时前
智能移动终端导航APP用户体验研究案例分享
人工智能·算法·人机交互·ux·用户界面·用户体验
dlraba8024 小时前
机器学习-----SVM(支持向量机)算法简介
算法·机器学习·支持向量机
_poplar_5 小时前
09 【C++ 初阶】C/C++内存管理
c语言·开发语言·数据结构·c++·git·算法·stl
2501_924747116 小时前
驾驶场景玩手机识别准确率↑32%:陌讯动态特征融合算法实战解析
人工智能·算法·计算机视觉·智能手机
limitless_peter7 小时前
优先队列,链表优化
c++·算法·链表