Leetcode 刷题记录 06 —— 矩阵

本系列为笔者的 Leetcode 刷题记录,顺序为 Hot 100 题官方顺序,根据标签命名,记录笔者总结的做题思路,附部分代码解释和疑问解答。

目录

01 矩阵置零

方法一:标记数组

方法二:两个标记变量

02 螺旋矩阵

方法一:模拟

方法二:按层模拟

03 旋转图像

方法一:辅助数组

方法二:原地旋转

方法三:用翻转代替旋转

04 搜索二维矩阵 Ⅱ

方法一:直接查找

方法二:二分法

方法三:Z字形查找


01 矩阵置零

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        
    }
};
方法一:标记数组

时间复杂度 O(mn),空间复杂度 O(m + n)

  • 建立两个数组 row(m)col(n),存储 matrix 中行和列的含零情况

  • 遍历数组,判断行或列含零,执行 matrix[i][j] = 0

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();
        vector<int> row(m), col(n);

        for(int i=0; i<m; ++i){
            for(int j=0; j<n; ++j){
                if(!matrix[i][j]){
                    row[i] = true;
                    col[j] = true;
                }
            }
        }

        for(int i=0; i<m; ++i){
            for(int j=0; j<n; ++j){
                if(row[i] || col[j]){
                    matrix[i][j] = 0;
                }
            }
        }
    }
};

vector<int> row(m), col(n);这俩数组没有初始化啥的吗,它们声明时的默认元素值是多少?

使用 vector<int> row(m) 这样的语句声明一个 vector并指定大小时, vector会自动初始化为指定大小的元素,且每个元素默认初始化为零。

方法二:两个标记变量

时间复杂度 O(mn),空间复杂度 O(1)

  • 建立两个标记 flag_col0flag_row0,存储 matrix 中除第零行和第零列的含零情况

  • 遍历数组,判断 !matrix[i][0] || !matrix[0][j],执行 matrix[i][j] = 0

  • 第零行和第零列单独更新

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int m = matrix.size(); //一共m行
        int n = matrix[0].size(); //一共n列
        int flag_col0 = false, flag_row0 = false;

        for(int i=0; i<m; ++i){
            if(!matrix[i][0]) flag_col0 = true;
        }
        for(int j=0; j<n; ++j){
            if(!matrix[0][j]) flag_row0 = true;
        }

        //处理大部分元素
        for(int i=1; i<m; ++i){
            for(int j=1; j<n; ++j){
                if(!matrix[i][j]){
                    matrix[i][0] = 0;
                    matrix[0][j] = 0;
                }
            }
        }
        for(int i=1; i<m; ++i){
            for(int j=1; j<n; ++j){
                if(!matrix[i][0] || !matrix[0][j]) {
                    matrix[i][j] = 0;
                }
            }
        }

        //处理第零行、第零列元素
        if(flag_col0){
            for(int i=0; i<m; ++i){
                matrix[i][0] = 0;
            }
        }
        if(flag_row0){
            for(int j=0; j<n; ++j){
                matrix[0][j] = 0;
            }
        }
    }
};

02 螺旋矩阵

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        
    }
};
方法一:模拟

时间复杂度 O(mn),空间复杂度 O(mn)

  • 建立二维数组 visited(rows, vector<bool>(columns),存储矩阵元素的访问情况

  • 遍历矩阵,判断 nextRownextColumn 是否越过边界

class Solution {
public:
    static constexpr int dirs[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; //向右、下、左、上

    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if (matrix.size() == 0 || matrix[0].size() == 0) return {};

        int rows = matrix.size(), columns = matrix[0].size(), total = rows * columns;
        vector<vector<bool>> visited(rows, vector<bool>(columns)); //标记
        vector<int> order(total); //答案

        int row = 0, column = 0, dirIndex = 0;
        for(int i=0; i<total; i++){
            order[i] = matrix[row][column]; //更新答案
            visited[row][column] = true; //更新标记
            
            int nextRow = row + dirs[dirIndex][0];
            int nextColumn = column + dirs[dirIndex][1];
            if(nextRow >= rows || nextRow < 0 || nextColumn >= columns || nextColumn < 0 || visited[nextRow][nextColumn]){
                dirIndex = (dirIndex + 1) % 4;
            }
            row += dirs[dirIndex][0];
            column += dirs[dirIndex][1];
        }
        return order;
    }
};

static 意味着变量在程序的生命周期内只会被分配一次,并且在所有对该数组的函数调用中共享同一存储空间。

constexpr 用于指定变量值在编译时就确定下来,它提示编译器尽量进行优化。

{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}每一对元素代表在二维平面上一个方向的坐标变换:

  • {0, 1}:代表向右移动 —— 行不变,列加一

  • {1, 0}:代表向下移动 —— 行加一,列不变

  • {0, -1}:代表向左移动 —— 行不变,列减一

  • {-1, 0}:代表向上移动 —— 行减一,列不变

vector<bool>(columns) 创建一个布尔向量,大小为 columns,其中每个元素默认初始化为 false

vector<vector<bool>>(rows, ...) 表示创建一个这样的布尔向量的向量,其长度为 rows,即每一行都是一个布尔向量,且每列都是初始化为 false

spiral 螺旋形的 constexpr 常量表达式

⑦ 在什么样的情况下 if (nextRow < 0 || nextRow >= rows || nextColumn < 0 || nextColumn >= columns || visited[nextRow][nextColumn]) 中的 nextRow < 0 成立?

由于初始位置是 (0, 0) 且遍历顺序是顺时针螺旋,因此 nextRow < 0 通常在当前方向反转尝试向上之前使用过的路径上被访问时发生。

⑧ 将代码中涉及 nextRownextColumn 部分的片段改为如下片段如何?

if((column == (columns - 1)) || (row == (rows - 1)) || (column == 0) || matrix[row + dirs[dirIndex][0]][column + dirs[dirIndex][1]]){
	dirIndex = (dirIndex + 1) % 4;
}

 matrix[row + dirs[dirIndex][0]][column + dirs[dirIndex][1]]:这种方式不仅增加了代码复杂度,并且可能由于超出 matrix 的边界而导致访问无效内存,出现内存错误。

if (column == columns || row == rows || column == -1 || row == -1 ||
row + dirs[dirIndex][0] < 0 || row + dirs[dirIndex][0] >= rows ||
column + dirs[dirIndex][1] < 0 || column + dirs[dirIndex][1] >= columns || 
visited[row + dirs[dirIndex][0]][column + dirs[dirIndex][1]]) {
dirIndex = (dirIndex + 1) % 4;
}

row + dirs[dirIndex][0] < 0:检查向当前方向移动后,新的行索引是否小于0。这会保持我们不越过上边界。

row + dirs[dirIndex][0] >= rows:检查向当前方向移动后,新的行索引是否大于或等于总行数。这会确保我们不越过下边界。

column + dirs[dirIndex][1] < 0:检查向当前方向移动后,新的列索引是否小于0。这会确保我们不越过左边界。

column + dirs[dirIndex][1] >= columns:检查向当前方向移动后,新的列索引是否大于或等于总列数。这会确保我们不越过右边界。

方法二:按层模拟

时间复杂度 O(mn),空间复杂度 O(1)

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if(matrix.size() == 0 || matrix[0].size() == 0) return {};

        vector<int> order;
        int rows = matrix.size(), columns = matrix[0].size();
        int left = 0, right = columns-1, top = 0, bottom = rows-1;
        
        //主打一个遍历
        while(left<=right && top<=bottom){
            for(int column=left; column<=right; ++column) {
                order.push_back(matrix[top][column]);
            }
            for(int row=top+1; row<=bottom; ++row) {
                order.push_back(matrix[row][right]);
            }
            if(left<right && top<bottom){
                for(int column=right-1; column>=left+1; --column) {
                order.push_back(matrix[bottom][column]);
                }
                for(int row=bottom; row>=top+1; --row) {
                    order.push_back(matrix[row][left]);
                }
            }
            top++;
            left++;
            right--;
            bottom--;
        }
        return order;
    }
};

① 为什么还要第二次判断 if(left<right && top<bottom) 呢?

在只有一行或者一列剩下时,第二次顺时针迭代会导致重复元素被添加到结果中。例如,当只剩下一行时,上面的第二次和第三次迭代(从右向左)会和已经处理的行产生重复。比如:

  • 单行(例如,[[1, 2, 3]]

  • 单列(例如,[[1], [2], [3]]

03 旋转图像

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        
    }
};
方法一:辅助数组

时间复杂度 O(n^2),空间复杂度 O(n^2)

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        auto matrix_new = matrix;
        
        int n = matrix.size();
        for(int i=0; i<n; ++i){
            for(int j=0, j<n; ++j){
                matrix_new[j][n-1-i] = matrix[i][j];
            }
        }
        return matrix_new;
    }
};

auto matrix_new = matrix;这行代码的作用是复制matrix变量的值到一个新的变量matrix_new中,matrix_new的类型与matrix一致。

对于大多数与标准库相关的容器(如 std::vector),这会创建 matrix 的一个浅拷贝,整体上是深拷贝其内容,而不是仅仅复制指针(如果它是一个复杂数据结构)。

方法二:原地旋转

时间复杂度 O(n^2),空间复杂度 O(1)

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();

        for(int x=0; x<n/2; ++x){
            for(int y=0; y<(n+1)/2; ++y){
                int flag = matrix[x][y];
                matrix[x][y] = matrix[n-1-y][x];
                matrix[n-1-y][x] = matrix[n-1-x][n-1-y];
                matrix[n-1-x][n-1-y] = matrix[y][n-1-x];
                matrix[y][n-1-x] = flag;
            }
        }
    }
};
方法三:用翻转代替旋转

时间复杂度 O(n^2),空间复杂度 O(1)

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();

        for(int x=0; x<n/2; ++x){
            for(int y=0; y<n; ++y){
                swap(matrix[x][y], matrix[n-1-x][y]);
            }
        }

        for(int x=0; x<n; ++x){
            for(int y=0; y<x; ++y){
                swap(matrix[x][y], matrix[y][x]);
            }
        }
    }
};

04 搜索二维矩阵 Ⅱ

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        
    }
};
方法一:直接查找

时间复杂度 O(mn),空间复杂度 O(1)

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        for(const auto& row: matrix){
            for(int element: row){
                if(element == target) return true;
            }
        }
        return false;
    }
};
方法二:二分法

时间复杂度 O(mlogn),空间复杂度 O(1)

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        for(const auto& row: matrix){
            auto it = lower_bound(row.begin(), row.end(), target);
            if(it != row.end() && *it == target) return true;
        }
        return false;
    }
};

lower_bound 是一个标准库函数,位于 <algorithm> 头文件中,用于在一个已排序的范围内查找目标值的位置。lower_bound 返回一个迭代器,指向范围内第一个不小于目标值的元素的位置,如果所有的元素都小于目标值,它将返回指向末尾的迭代器。

注意:使用 lower_bound  的前提是 row 必须是排序好的,否则结果是不确定的。

方法三:Z字形查找
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size(), n = matrix[0].size();
        int x = 0, y = n-1;
        while(x < m && y >=0){
            if(matrix[x][y] == target) return true;
            else if(matrix[x][y] > target) y--;
            else x++;
        }
        return false;
    }
};

文章部分代码来源于力扣(LeetCode)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/984149.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Elasticsearch:使用 BigQuery 提取数据

作者&#xff1a;来自 Elastic Jeffrey Rengifo 了解如何使用 Python 在 Elasticsearch 中索引和搜索 Google BigQuery 数据。 BigQuery 是 Google 的一个平台&#xff0c;允许你将来自不同来源和服务的数据集中到一个存储库中。它还支持数据分析&#xff0c;并可使用生成式 AI…

如何在el-input搜索框组件的最后面,添加图标按钮?

1、问题描述 2、解决步骤 在el-input组件标签内&#xff0c;添加一个element-plus的自定义插槽&#xff0c; 在插槽里放一个图标按钮即可。 3、效果展示 结语 以上就是在搜索框组件的末尾添加搜索按钮的过程。 喜欢本篇文章的话&#xff0c;请关注本博主~~

Magento2根据图片文件包导入产品图片

图片包给的图片文件是子产品的图片&#xff0c;如下图&#xff1a;A104255是主产品的sku <?php/*** 根据图片包导入产品图片&#xff0c;包含子产品和主产品* 子产品是作为主图&#xff0c;主产品是作为附加图片*/use Magento\Framework\App\Bootstrap;include(../app/boot…

INT_MAX 与 0x3f3f3f3f 的区别

【INT_MAX 与 0x3f3f3f3f 的区别】 在算法设计中&#xff0c;INT_MAX 与 0x3f3f3f3f 都常被用作无穷大值的设定&#xff0c;但二者区别显著&#xff0c;适用场景也有所不同。 备注&#xff1a;此图由百度 AI 创作生成 &#xff08;1&#xff09;INT_MAX 是 C/C 中的标准常量&a…

升级旧版本Vmware到Vmware Workstation Pro 17

背景 一些新版本Linux内核版本较高&#xff0c;例如&#xff1a;openEuler24.03 LTS需要的内核版本为6.6&#xff0c;而Vmware Workstation Pro 16最高只支持Linux5.x内核&#xff0c;对Linux6.x不支持&#xff0c;因此&#xff0c;需要将旧版本的Vmware升级到Vmware Workstat…

【第23节】C++设计模式(行为模式)-Interpreter(解释器)模式

一、问题背景 在一些应用中&#xff0c;系统会提供内建&#xff08;Build-In&#xff09;的脚本或宏语言&#xff0c;允许用户定义他们能够在系统中执行的操作。Interpreter 模式的目的就是为用户提供一种定义语言的语法表示&#xff0c;并通过解释器来解释语言中的句子。这种模…

哈弗赛恩公式计算长度JavaScript实现

哈弗赛恩公式&#xff08;Haversine formula&#xff09;是一种用于计算球面上两点间最短距离的数学方法&#xff0c;尤其适用于地球表面。本文将详细介绍哈弗赛恩公式的原理、应用以及如何使用JavaScript实现它。 一、哈弗赛恩公式原理 在球面几何中&#xff0c;哈弗赛恩公式…

Day05 实例:正向反向连接内外网环境防火墙出入站

一、正反向连接 0、先将防火墙关闭 Linux&#xff1a; sudo systemctl stop firewalld Windows&#xff1a;netsh advfirewall set allprofiles state off 1、正向连接 1.1 Linux连接Windows 00x1 开启两台服务器 并且给Windows拖入nc.exe 00x2 Windows绑定自己5566端…

【大模型知识点】位置编码——绝对位置编码,相对位置编码,旋转位置编码RoPE

由于Transformer 中的自注意模块具有置换不变性&#xff08;不关心输入序列的顺序&#xff09;&#xff0c;因此需要使用位置编码来注入位置信息以建模序列&#xff0c;使模型能够区分不同位置的 token&#xff0c;并捕捉序列的顺序关系。 在介绍一些位置编码方法前&#xff0…

Linux 配置静态 IP

一、简介 在 Linux CentOS 系统中默认动态分配 IP 地址&#xff0c;每次启动虚拟机服务都是不一样的 IP&#xff0c;因此要配置静态 IP 地址避免每次都发生变化&#xff0c;下面将介绍配置静态 IP 的详细步骤。 首先先理解一下动态 IP 和静态 IP 的概念&#xff1a; 动态 IP…

DeepSeek 3FS:端到端无缓存的存储新范式

在 2025 年 2 月 28 日&#xff0c;DeepSeek 正式开源了其高性能分布式文件系统 3FS【1】&#xff0c;作为其开源周的压轴项目&#xff0c;3FS 一经发布便引发了技术圈的热烈讨论。它不仅继承了分布式存储的经典设计&#xff0c;还通过极简却高效的架构&#xff0c;展现了存储技…

微服务与消息队列RabbitMQ

简介 同步模式 异步模式 内容 解决方案RabbitMQ 同步调用的优缺点 同步调用的优势是什么&#xff1f; 时效性强&#xff0c;等待到结果后才返回。 同步调用的问题是什么&#xff1f; 拓展性差性能下降级联失败问题

vue+element-plus简洁完美实现古诗文网

目录 一、项目介绍 二、项目截图 1.项目结构图 2.首页&#xff08;推荐&#xff09; 3.诗文 4.名句 5.作者 6.古籍 7.我的 三、源码实现 1.路由配置 2.顶部 四、总结 一、项目介绍 项目在线预览&#xff1a;点击访问 本项目为vue项目&#xff0c;参考古诗文网官方…

Vue项目通过内嵌iframe访问另一个vue页面,获取token适配后端鉴权(以内嵌若依项目举例)

1. 改造子Vue项目进行适配(ruoyi举例) (1) 在路由文件添加需要被外链的vue页面配置 // 若依项目的话是 router/index.js文件 {path: /contrast,component: () > import(/views/contrast/index),hidden: true },(2) 开放白名单 // 若依项目的话是 permission.js 文件 cons…

RuleOS:区块链开发的“破局者”,开启Web3新纪元

RuleOS&#xff1a;区块链开发的“破冰船”&#xff0c;驶向Web3的星辰大海 在区块链技术的浩瀚宇宙中&#xff0c;一群勇敢的探索者正驾驶着一艘名为RuleOS的“破冰船”&#xff0c;冲破传统开发的冰层&#xff0c;驶向Web3的星辰大海。这艘船&#xff0c;正以一种前所未有的姿…

指针的工作原理,函数的传值和传址

在C之中&#xff0c;指针是一个变量用于存储另外一个变量的内存地址。指针可以指向各种数据类型例如基础数据类型和自定义数据类型等。 在计算机之中的内存被划分为一个一个的存储单元&#xff0c;每个存储单元拥有唯一的内存地址&#xff0c;指针就是指向这些内存单元的地址。…

RISC-V汇编学习(三)—— RV指令集

有了前两节对于RISC-V汇编、寄存器、汇编语法等的认识&#xff0c;本节开始介绍RISC-V指令集和伪指令。 前面说了RISC-V的模块化特点&#xff0c;是以RV32I为作为ISA的核心模块&#xff0c;其他都是要基于此为基础&#xff0c;可以这样认为&#xff1a;RISC-V ISA 基本整数指…

基于控制障碍函数(Control Barrier Function)的二次规划(QP)控制

文章目录 研究背景主要贡献障碍函数&#xff08;Barrier Function&#xff09;&#xff08;一&#xff09;倒数障碍函数&#xff08;Reciprocal Barrier Function, RBF&#xff09;&#xff08;二&#xff09;归零障碍函数&#xff08;Zeroing Barrier Function, ZBF&#xff0…

软件工程:软件需求之需求分析方法

目录 前言 需求分析方法 工具和方法 具体分析方法 对运行环境的影响 ​编辑 前言 本文重点介绍开展软件需求分析的方法。 需求分析方法 工具和方法 软件需求可以维护在ALM系统中&#xff0c;譬如&#xff1a;doors&#xff0c;codeBeamer等&#xff0c;JIRA适合互联网行…

蓝桥杯—走迷宫(BFS算法)

题目描述 给定一个NM 的网格迷宫 G。G 的每个格子要么是道路&#xff0c;要么是障碍物&#xff08;道路用 11表示&#xff0c;障碍物用 0 表示&#xff09;。 已知迷宫的入口位置为 (x1​,y1​)&#xff0c;出口位置为 (x2​,y2​)。问从入口走到出口&#xff0c;最少要走多少…