【刷题(12)】图论

一、图论问题基础

在 LeetCode 中,「岛屿问题」是一个系列系列问题,比如:

  • 岛屿数量 (Easy)
  • 岛屿的周长 (Easy)
  • 岛屿的最大面积 (Medium)
  • 最大人工岛 (Hard)
    我们所熟悉的 DFS(深度优先搜索)问题通常是在树或者图结构上进行的。而我们今天要讨论的 DFS 问题,是在一种「网格」结构中进行的。岛屿问题是这类网格 DFS 问题的典型代表。网格结构遍历起来要比二叉树复杂一些,如果没有掌握一定的方法,DFS 代码容易写得冗长繁杂。

网格类问题的 DFS 遍历方法
网格问题的基本概念
我们首先明确一下岛屿问题中的网格结构是如何定义的,以方便我们后面的讨论。

网格问题是由 m×nm \times nm×n 个小方格组成一个网格,每个小方格与其上下左右四个方格认为是相邻的,要在这样的网格上进行某种搜索。

岛屿问题是一类典型的网格问题。每个格子中的数字可能是 0 或者 1。我们把数字为 0 的格子看成海洋格子,数字为 1 的格子看成陆地格子,这样相邻的陆地格子就连接成一个岛屿。
在这里插入图片描述
DFS 的基本结构
网格结构要比二叉树结构稍微复杂一些,它其实是一种简化版的图结构。要写好网格上的 DFS 遍历,我们首先要理解二叉树上的 DFS 遍历方法,再类比写出网格结构上的 DFS 遍历。我们写的二叉树 DFS 遍历一般是这样的:

void traverse(TreeNode root) {// 判断 base caseif (root == null) {return;}// 访问两个相邻结点:左子结点、右子结点traverse(root.left);traverse(root.right);
}

可以看到,二叉树的 DFS 有两个要素:「访问相邻结点」和「判断 base case」。

第一个要素是访问相邻结点。二叉树的相邻结点非常简单,只有左子结点和右子结点两个。二叉树本身就是一个递归定义的结构:一棵二叉树,它的左子树和右子树也是一棵二叉树。那么我们的 DFS 遍历只需要递归调用左子树和右子树即可。

第二个要素是 判断 base case。一般来说,二叉树遍历的 base case 是 root == null。这样一个条件判断其实有两个含义:一方面,这表示 root 指向的子树为空,不需要再往下遍历了。另一方面,在 root == null 的时候及时返回,可以让后面的 root.left 和 root.right 操作不会出现空指针异常。

对于网格上的 DFS,我们完全可以参考二叉树的 DFS,写出网格 DFS 的两个要素:

首先,网格结构中的格子有多少相邻结点?答案是上下左右四个。对于格子 (r, c) 来说(r 和 c 分别代表行坐标和列坐标),四个相邻的格子分别是 (r-1, c)、(r+1, c)、(r, c-1)、(r, c+1)。换句话说,网格结构是「四叉」的。
在这里插入图片描述
其次,网格 DFS 中的 base case 是什么?从二叉树的 base case 对应过来,应该是网格中不需要继续遍历、grid[r][c] 会出现数组下标越界异常的格子,也就是那些超出网格范围的格子。
在这里插入图片描述
这一点稍微有些反直觉,坐标竟然可以临时超出网格的范围?这种方法我称为「先污染后治理」—— 甭管当前是在哪个格子,先往四个方向走一步再说,如果发现走出了网格范围再赶紧返回。这跟二叉树的遍历方法是一样的,先递归调用,发现 root == null 再返回。

这样,我们得到了网格 DFS 遍历的框架代码:

void dfs(int[][] grid, int r, int c) {// 判断 base case// 如果坐标 (r, c) 超出了网格范围,直接返回if (!inArea(grid, r, c)) {return;}// 访问上、下、左、右四个相邻结点dfs(grid, r - 1, c);dfs(grid, r + 1, c);dfs(grid, r, c - 1);dfs(grid, r, c + 1);
}// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length;
}

如何避免重复遍历
网格结构的 DFS 与二叉树的 DFS 最大的不同之处在于,遍历中可能遇到遍历过的结点。这是因为,网格结构本质上是一个「图」,我们可以把每个格子看成图中的结点,每个结点有向上下左右的四条边。在图中遍历时,自然可能遇到重复遍历结点。

这时候,DFS 可能会不停地「兜圈子」,永远停不下来,如下图所示:

在这里插入图片描述
如何避免这样的重复遍历呢?答案是标记已经遍历过的格子。以岛屿问题为例,我们需要在所有值为 1 的陆地格子上做 DFS 遍历。每走过一个陆地格子,就把格子的值改为 2,这样当我们遇到 2 的时候,就知道这是遍历过的格子了。也就是说,每个格子可能取三个值:

0 —— 海洋格子
1 —— 陆地格子(未遍历过)
2 —— 陆地格子(已遍历过)
我们在框架代码中加入避免重复遍历的语句:

void dfs(int[][] grid, int r, int c) {// 判断 base caseif (!inArea(grid, r, c)) {return;}// 如果这个格子不是岛屿,直接返回if (grid[r][c] != 1) {return;}grid[r][c] = 2; // 将格子标记为「已遍历过」// 访问上、下、左、右四个相邻结点dfs(grid, r - 1, c);dfs(grid, r + 1, c);dfs(grid, r, c - 1);dfs(grid, r, c + 1);
}// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length;
}

在这里插入图片描述
这样,我们就得到了一个岛屿问题、乃至各种网格问题的通用 DFS 遍历方法。以下所讲的几个例题,其实都只需要在 DFS 遍历框架上稍加修改而已。

小贴士:
在一些题解中,可能会把「已遍历过的陆地格子」标记为和海洋格子一样的 0,美其名曰「陆地沉没方法」,即遍历完一个陆地格子就让陆地「沉没」为海洋。这种方法看似很巧妙,但实际上有很大隐患,因为这样我们就无法区分「海洋格子」和「已遍历过的陆地格子」了。如果题目更复杂一点,这很容易出 bug。

二、200. 岛屿数量

1 题目

在这里插入图片描述

2 解题思路

(1)网格问题其实是一种特殊的四叉树,我们可以使用DFS,BFS来解这道题。
(2)使用‘2’或’0’来标记已经遍历过的陆地。

3 code

class Solution {
public:int rowCount;int colCount;int numIslands(vector<vector<char>>& grid) {this->rowCount = grid.size();this->colCount = grid[0].size();// 用来记录岛屿数量int num_islands = 0;for (int row = 0; row < rowCount; row++) {for (int col = 0; col < colCount; col++) {// 如果当前位置是岛屿的一部分if (grid[row][col] == '1') {// 岛屿数量增加num_islands++;// 从当前位置开始执行DFS, 标记整个岛屿DFS(grid, row, col);}}}return num_islands;}void DFS(vector<vector<char>>& grid, int row, int col) {// 将当前位置标记为'0', 表示已访问grid[row][col] = '2';// 检查并递归访问当前点的上下左右四个相邻点if (row - 1 >= 0 && grid[row - 1][col] == '1') DFS(grid, row - 1, col);if (row + 1 < rowCount && grid[row + 1][col] == '1') DFS(grid, row + 1, col);if (col - 1 >= 0 && grid[row][col - 1] == '1') DFS(grid, row, col - 1);if (col + 1 < colCount && grid[row][col + 1] == '1') DFS(grid, row, col + 1);}
};

三、994. 腐烂的橘子

1 题目

在这里插入图片描述

2 解题思路 广度优先搜索(BFS)

(1)首先分别将腐烂的橘子和新鲜的橘子保存在两个集合中;
(2)模拟广度优先搜索的过程,方法是判断在每个腐烂橘子的四个方向上是否有新鲜橘子,如果有就腐烂它。每腐烂一次时间加 111,并剔除新鲜集合里腐烂的橘子;
(3)当橘子全部腐烂时结束循环。
在这里插入图片描述
注:一般使用如下方法实现四个方向的移动:

# 设初始点为 (i, j)
for di, dj in [(0, 1), (0, -1), (1, 0), (-1, 0)]: # 上、下、左、右i + di, j + dj

3 code

class Solution {int dirt[4][2] = {{-1,0},{1,0},{0,1},{0,-1}};
public:int orangesRotting(vector<vector<int>>& grid) {//记录所需要腐烂的分钟int min = 0;//记录新鲜橘子的数量int fresh = 0;//记录腐烂水果坐标queue<pair<int,int>>que;//遍历地图for(int i = 0;i<grid.size();i++){for(int j = 0;j<grid[0].size();j++){if(grid[i][j]==1){fresh++;}else if (grid[i][j] ==2){que.push({i,j});}}}while(!que.empty()){int n = que.size();bool rotten = false;//遍历队列一层的元素for(int i= 0;i<n;i++){auto x = que.front();   //保存腐烂元素的坐标que.pop();      //出队列for(auto cur: dirt){int i = x.first + cur[0];   //更新x的坐标int j = x.second + cur[1];  //更新y的坐标//向四个方向遍历if(i>=0 && i<grid.size()&&j>=0&&j<grid[0].size()&&grid[i][j]==1){grid[i][j] = 2;     //更新坐标que.push({i,j});    //加入队列fresh--;            //新鲜数量减一rotten = true;      //标记遍历完一层}}}if(rotten) min++; //遍历完一层,记录+1}return fresh ? -1:min;}
};

四、207. 课程表

1 题目

在这里插入图片描述

2 解题思路

(1)题目给的用例不太明显。的另外举例子。输入:3,[ [0,1] , [1,2] , [2,0] ],对于这个用例。我把图画出来。
在这里插入图片描述
按照示例的解释是这样的:总共有 3 门课程。学习课程 2 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。学习课程 1 之前,你需要先完成​课程 2。这是不可能的。
仔细观察就发现,这个图是有向图,并且形成了一个环。(从n点出发,最终还能回到n点),所以返回false
那这个题目就变成了:
判断有向图,是否有环。 有返回false,没有返回true
(2)那我怎么用深度优先遍历(dfs)判断有向图是否有环呢。其实很简单。
如果你写过深度优先搜索遍历。那就很简单了。
拿邻接表来解释深度优先未免有些复杂,我再画一张图
输入:4,[ [0,2], [1,0], [1,3], [3,0] ]
在这里插入图片描述
为了清晰起见,我解释一下dfs的过程。

设置一个visit数组(开节点个数),初始为0,visit =1 表示被访问过了。

我们要对每一个点进行一次深度遍历,看它是否形成环。

对 3 dfs:
visit[3]=0,3没被标记过,标记visit[3]=1, 对3进行dfs,访问和3相连接的所有点(0),
visit[0]=0,0没被标记过,标记visit[0]=1, 对0进行dfs,访问和0相连接的所有点(2),
visit[2]=0,2没被标记过,标记visit[2]=1, 对2进行dfs,访问和2相连接的所有点
(没有和2相连接的点,dfs终止,并没有环,返回true, 开始回溯)

对 2 dfs:…
对 1 dfs:…
对 0 dfs:…

回溯的时候要把visit还原为0。

递归你们都应该清楚,太麻烦就省略了,总之就是访问一个节点,就对它所有相连接的点进行dfs,这个是深度遍历的标准思路。只是加了个标记数组。

性能上的优化:我们可以在回溯的时候,把visit设置为-1,表示这个点之前已经被访问过了,走这点没环。
这样我们进入dfs后,如果visit等于 -1 ,直接返回true。

这个性能优化提速是非常明显的。虽然没优化也能通过。

3 code

class Solution {
public:vector<int>visit;bool dfs(int v,vector<vector<int>>& g){if (g[v].size() == 0)   //没相邻的节点了,返回truereturn true;if (visit[v] == -1)   //走这节点没环,返回truereturn true;if (visit[v] == 1)  //被标记过了,存在环,返回falsereturn false;visit[v] = 1;  //标记bool res = true;for (int i = 0; i < g[v].size(); i++)  //访问v节点的所有相连接的节点,对于每个节点都进行dfs{res = dfs(g[v][i], g);if (res == false)break;}visit[v] =-1 ;  //回溯时设置visit为-1return res;}bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {vector<vector<int>> g(numCourses);visit = vector<int>(numCourses + 1, 0);//建立有向邻接表for (int i = 0; i < prerequisites.size(); i++)g[prerequisites[i][0]].push_back(prerequisites[i][1]);bool res = true;for(int i =0;i<numCourses;i++)  //对每个节的所有相连接的点进行dfs(深度优先遍历)for (int j = 0; j < g[i].size(); j++){res = dfs(g[i][j], g);if (res == false)return res;}return res;}
};

五、208. 实现Trie(前缀树)

1 题目

在这里插入图片描述

2 解题思路

3 code

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【考研数学】数学一和数学二哪个更难?如何复习才能上90分?

很明显考研数学一更难&#xff01; 不管是复习量还是题目难度 对比项考研数学一考研数学二适用专业理工科类及部分经济学类理工科类考试科目高等数学、线性代数、概率论与数理统计高等数学、线性代数试卷满分150分150分考试时间180分钟180分钟试卷内容结构高等数学约60%&…

电脑怎么清理c盘垃圾文件 电脑运行内存不足怎么清理

和Windows系统电脑文件分区不同&#xff0c;苹果电脑并不分区&#xff0c;默认只有C盘&#xff0c;当C盘垃圾文件过多&#xff0c;电脑运行内存不足时&#xff0c;手动清理电脑垃圾文件毫无头绪&#xff0c;可以尝试使用苹果电脑清理软件——CleanMyMac来清理 。 一、电脑怎么…

React Hooks是如何保存的

React 函数式组件是没有状态的&#xff0c;需要 Hooks 进行状态的存储&#xff0c;那么状态是怎么存储的呢&#xff1f;Hooks是保存在 Fiber 树上的&#xff0c;多个状态是通过链表保存&#xff0c;本文将通过源代码分析 Hooks 的存储位置。 创建组件 首先我们在组件中添加两…

电商推荐系统+电影推荐系统【虚拟机镜像分享】

电商推荐系统电影推荐系统【虚拟机镜像分享】 所有组件部署好的镜像下载&#xff08;在下面&#xff09;&#xff0c;仅供参考学习。&#xff08;百度网盘&#xff0c;阿里云盘…&#xff09; 博主通过学习尚硅谷电商推荐电影推荐项目&#xff0c;将部署好的虚拟机打包成ovf文…

设计模式复习

一、模式所采用的关系&#xff08;e.g.继承…&#xff09; UML图例 二、各模式的特点、优缺点 1.创建型 将对象的使用和创建分离&#xff0c;使用对象时无需知道对象的创建细节&#xff0c;使得创建过程可以多次复用&#xff0c;且修改两者中的一个对另一个影响为0或很少。 …

Stable Diffusion WebUI详细使用指南

Stable Diffusion WebUI&#xff08;AUTOMATIC1111&#xff0c;简称A1111&#xff09;是一个为高级用户设计的图形用户界面&#xff08;GUI&#xff09;&#xff0c;它提供了丰富的功能和灵活性&#xff0c;以满足复杂和高级的图像生成需求。由于其强大的功能和社区的活跃参与&…

连锁收银系统支持带结算功能

连锁实体店的收银系统需要支持结算功能&#xff0c;以适应连锁运营效率和提升连锁管理的水平。商淘云连锁收银系统与您一起分享连锁收银系统需支持结算功能的三大必要点。大家点赞收藏&#xff0c;以免划走后找不到了。 一是&#xff0c;连锁模式的运营比较复杂&#xff0c;有加…

速看!!24上软考【电子商务设计师】真题回顾,含答案解析

2024上半年软考考试已经结束了&#xff0c;为大家整理了网友回忆版的电子商务设计师真题及答案&#xff0c;25-26日两批考试总共60道题。 上半年考试的宝子们可以对答案预估分数&#xff01;准备下半年考的宝子可以提前把握考试知识点和出题方向&#xff0c;说不定会遇到相同考…

C++学习~~对于二进制文件的读写命名空间再认识异常处理

目录 1.将数据以二进制形式放到磁盘 2.将上述的数据读入内存并且显示在显示器上面 3.异常处理机制 4.抛出异常的应用实例 1.将数据以二进制形式放到磁盘 &#xff08;1&#xff09;使用student定义结构体数组stud,并对其进行初始化&#xff0c;创建输出文件流对象outfile,这…

STL库--stack

目录 stack的定义 stack容器内元素的访问 stack常用函数实例解析 stack的常见用途 stack的定义 其定义的写法和其他STL容器相同&#xff0c;typename可以任意基本类型或容器&#xff1a; stack<typename> name; stack容器内元素的访问 由于栈本身就是一种后进先出…

全球市值最高的能源公司沙特阿美股份拟出售,筹集百亿美元

KlipC报道&#xff1a;据5月28日市场消息&#xff0c;沙特政府可能最快会在本周宣布拟出售国营石油公司沙特阿美股份&#xff0c;筹集100亿-200亿美元。 沙特阿美是世界最大的石油生产商&#xff0c;2019年在沙特证交所上市。沙特的经济高度依赖石油出口。此前&#xff0c;受石…

论文笔记 Explicit Visual Prompting for Low-Level Structure Segmentations

通俗地解释视觉中的prompt 在视觉中的“prompt”&#xff08;提示&#xff09;可以用一种比较通俗的方式来理解&#xff1a; 什么是视觉中的提示&#xff1f; 想象一下&#xff0c;你有一个已经接受过大量训练的超级助手&#xff08;类似于预训练的模型&#xff09;&#xf…

【IOT】OrangePi+HomeAssistant+Yolov5智能家居融合

前言 本文将以OrangePi AIpro为基础&#xff0c;在此基础构建HomeAssistant、YOLO目标检测实现智能家居更加灵活智能的场景实现。 表头表头设备OrangePi AIpro(8T)系统版本Ubuntu 22.04.4 LTSCPU4核64位处理器 AI处理器AI算力AI算力 8TOPS算力接口HDMI2、GPIO接口、Type-C、M.2…

【YOLOv10】2024年5月最新的YOLO系列模型Yolov10(论文阅读笔记) + 完整创新点说明 + 总结

&#x1f680;&#x1f680;&#x1f680; YOLOv10: 实时端到端的目标检测。YOLOv10比最先进的YOLOv9延迟时间更低&#xff0c;测试结果可以与YOLOv9媲美&#xff0c;可能会成为YOLO系列模型部署的“新选择”。 官方论文地址&#xff1a;https://arxiv.org/pdf/2405.14458 官方…

怎么更改图片格式?图片在线转格式的使用方法

现在很多的平台在上传图片的时候&#xff0c;都会有规定要求的大小、格式、尺寸&#xff0c;只有符合要求的图片才可以正常上传。在网上传图时想要快速的修改图片格式&#xff0c;比较简单的一个方法就是在使用在线图片格式转换的工具就能够快速处理&#xff0c;下面将图片转格…

关于微信小程序低功耗蓝牙ECharts实时刷新(涉及自定义缓冲区)

简单的蓝牙显示&#xff08;串口手动发数据测试&#xff09; 最近搞了这方面的东西&#xff0c;是刚刚开始接触微信小程序&#xff0c;因为是刚刚开始接触蓝牙设备&#xff0c;所以这篇文章适合既不熟悉小程序&#xff0c;又不熟悉蓝牙的新手看。 项目要求是获取到蓝牙传输过来…

在线思维导图编辑!3个AI思维导图生成软件推荐!

思维导图&#xff0c;一种以创新为驱动的视觉化思考工具&#xff0c;已经渗透到我们日常生活和工作的各个角落。当我们需要整理思绪、规划项目或者梳理信息时&#xff0c;思维导图总能提供极大的帮助。 近些年随着云服务等基础设施的完善&#xff0c;我们可以看到越来越多提供…

ITIL4认证考试这么贵,还值得考证吗,有必要学吗?

从2023年4月1日开始&#xff0c;ITIL 4是Foundation认证将会捆绑OTM(Official Training Materials),这样在一次ITIL4的考试费中将会捆绑&#xff1a;试卷费电子教材书费监考费OTM费&#xff0c;每一种考试费都相较于2022年有涨幅&#xff0c;再加上PeopleCert收取的授权机构的授…

视频监控业务平台LntonCVS国标GB28181视频平台智慧城市应用方案

随着科技的不断进步&#xff0c;尤其是人工智能技术的飞速发展&#xff0c;视频应用已经超越了传统的视频监控、视频会议、视频通话和视频指挥调度等基本功能。它们正在向更加多元化、灵活化、融合化和智能化的方向发展。因此&#xff0c;建立一个视频AI中台变得至关重要。 通过…

持续领跑教育科技,网易有道再发“子曰”教育大模型全新应用

5月29日&#xff0c;网易有道“子曰”教育大模型媒体交流会在北京举行。会上&#xff0c;网易有道分享了子曰教育大模型最新技术进展及三大AI创新应用&#xff1a;AI全科学习助手“有道小P”APP、新一代虚拟人口语教练Hi Echo 3.0和新一代知识库问答引擎QAnything。 现场&…