【代码随想录训练营第42期 续Day52打卡 - 图论Part3 - 卡码网 103. 水流问题 104. 建造最大岛屿

news2024/12/29 8:00:41

目录

一、做题心得

二、题目与题解

题目一:卡码网 103. 水流问题

题目链接

题解:DFS

题目二:卡码网 104. 建造最大岛屿

题目链接

题解:DFS

 三、小结


一、做题心得

也是成功补上昨天的打卡了。

这里继续图论章节,还是选择使用 DFS 来解决这类搜索问题(单纯因为我更熟悉 DFS 一点),今天补卡的是水流问题和岛屿问题。个人感觉这一章节题对于刚入门图论还是挺有难度的,我们需要搞清楚 DFS 函数的作用,以及具体的代码书写,然后才是解题思路和优化的问题,毕竟这类题 DFS 函数部分的实现代码变化不大。

直接开始今天的内容。

二、题目与题解

题目一:卡码网 103. 水流问题

题目链接

103. 水流问题 (kamacoder.com)

题目描述

现有一个 N × M 的矩阵,每个单元格包含一个数值,这个数值代表该位置的相对高度。矩阵的左边界和上边界被认为是第一组边界,而矩阵的右边界和下边界被视为第二组边界。

矩阵模拟了一个地形,当雨水落在上面时,水会根据地形的倾斜向低处流动,但只能从较高或等高的地点流向较低或等高并且相邻(上下左右方向)的地点。我们的目标是确定那些单元格,从这些单元格出发的水可以达到第一组边界和第二组边界。

输入描述

第一行包含两个整数 N 和 M,分别表示矩阵的行数和列数。 

后续 N 行,每行包含 M 个整数,表示矩阵中的每个单元格的高度。

输出描述

输出共有多行,每行输出两个整数,用一个空格隔开,表示可达第一组边界和第二组边界的单元格的坐标,输出顺序任意。

输入示例

5 5
1 3 1 2 4
1 2 1 3 2
2 4 7 2 1
4 5 6 1 1
1 4 1 2 1

输出示例

0 4
1 3
2 2
3 0
3 1
3 2
4 0
4 1

提示信息

图中的蓝色方块上的雨水既能流向第一组边界,也能流向第二组边界。所以最终答案为所有蓝色方块的坐标。 

数据范围:

1 <= M, N <= 100。

题解:DFS

题意:水会根据地形的倾斜向低处流动,但只能从较高或等高的地点流向较低或等高并且相邻(上下左右方向)的地点,目标是确定那些单元格,从这些单元格出发的水可以达到第一组边界和第二组边界。

思路:选择逆向思维从第一组边界上的节点逆流而上,将遍历过的节点都标记上,同样从第二组边界的边上节点逆流而上,将遍历过的节点也标记上。然后两方都标记过的节点就是满足题意的坐标。

思路理清之后这题就不难了,分别从两组边界(4条边:上下左右边缘)开始进行 DFS 遍历并分别标记并记录两组逆向标记的结果,最终题目要求的节点就是两组标记结果的公共节点(都有标记)。

两组逆向标记的结果 -- 注意是 bool 类型对遍历的节点进行标记

第一组边界:firstBorder

第二组边界:secondBorder

其他就跟之前打卡的岛屿问题差不多,这里不做过多解释,代码如下:

#include <bits/stdc++.h>
using namespace std;

int n, m;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
void dfs(vector<vector<int>> &grid, vector<vector<bool>> &visited, int curx, int cury)
{
    if (visited[curx][cury]) // 如果当前节点已经被访问过,直接返回
        return;
    visited[curx][cury] = true; // 标记当前节点为已访问
    for (int i = 0; i < 4; i++)
    {
        int nextx = curx + dx[i];
        int nexty = cury + dy[i];
        if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m)
            continue;
        if (grid[curx][cury] > grid[nextx][nexty]) // 注意:我们是逆向从低向高遍历 -- 那么从高到低(包括相等)情况直接跳过
            continue;
        dfs(grid, visited, nextx, nexty);
    }
    return;
}
int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            cin >> grid[i][j];
        }
    }
    // 标记从第一组边界上的节点出发,可以遍历的节点
    vector<vector<bool>> firstBorder(n, vector<bool>(m, false));
    // 标记从第二组边界上的节点出发,可以遍历的节点
    vector<vector<bool>> secondBorder(n, vector<bool>(m, false));
    // 从最上和最下行的节点出发,向高处遍历 -- 逆向
    for (int i = 0; i < n; i++)
    {
        dfs(grid, firstBorder, i, 0);      // 遍历最左列,接触第一组边界
        dfs(grid, secondBorder, i, m - 1); // 遍历最右列,接触第二组边界
    }
    // 从最左和最右列的节点出发,向高处遍历 -- 逆向
    for (int j = 0; j < m; j++)
    {
        dfs(grid, firstBorder, 0, j);      // 遍历最上行,接触第一组边界
        dfs(grid, secondBorder, n - 1, j); // 遍历最下行,接触第二组边界
    }
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            if (firstBorder[i][j] && secondBorder[i][j]) // 如果这个节点,从第一组边界和第二组边界出发都遍历过(都有标记),就是结果
                cout << i << " " << j << endl;
        }
    }
}

题目二:卡码网 104. 建造最大岛屿

题目链接

104. 建造最大岛屿 (kamacoder.com)

题目描述

给定一个由 1(陆地)和 0(水)组成的矩阵,你最多可以将矩阵中的一格水变为一块陆地,在执行了此操作之后,矩阵中最大的岛屿面积是多少。

岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设矩阵外均被水包围。

输入描述

第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。

输出描述

输出一个整数,表示最大的岛屿面积。

输入示例

4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1

输出示例

6

提示信息

对于上面的案例,有两个位置可将 0 变成 1,使得岛屿的面积最大,即 6。

数据范围:

1 <= M, N <= 50。

题解:DFS

之前打卡的岛屿问题的升级版。

本题的关键在于:如何有效地标记和计算每个岛屿的面积,以及如何找出添加一块陆地后能够连接的最大岛屿面积之和。

几个实现点:

深度优先搜索(DFS):使用 dfs 函数来遍历地图,标记陆地,并计算岛屿面积。dfs 函数通过递归调用,遍历与当前节点相邻的陆地节点,并给每个陆地节点标记一个唯一的岛屿编号。

岛屿面积计算:在 dfs 函数中,通过一个计数器 cnt 来记录当前岛屿的面积。每当访问一个新的陆地节点时,cnt 加1。最后,将岛屿面积记录在哈希表 hash 中,键是岛屿编号,值是岛屿面积。

标记已访问的岛屿:使用一个布尔数组 visited 来标记每个节点是否已经被访问过。在 dfs 函数中,如果当前节点已经被访问过或者遇到海水,则直接返回,避免重复访问。

岛屿编号:岛屿编号从2开始,因为1已经被用作陆地,避免混淆。在 dfs 函数中,给每个陆地节点赋予一个新的岛屿编号。

计算添加陆地后的最大岛屿面积之和:遍历整个地图,对于每个位置,如果当前位置是陆地,则计算添加陆地后的岛屿面积之和。这通过遍历当前位置的四个相邻节点来实现,将相邻岛屿的面积加起来,并记录最大值。

代码如下:

#include <bits/stdc++.h>
using namespace std;

int n, m;
int cnt;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
void dfs(vector<vector<int>> &grid, vector<vector<bool>> &visited, int x, int y, int mark) // dfs函数用于标记岛屿,并计算岛屿面积
{
    if (visited[x][y] || grid[x][y] == 0) // 终止条件:访问过的节点 或者 遇到海水
        return;
    visited[x][y] = true; // 标记当前节点为已访问
    grid[x][y] = mark;    // 给陆地标记新标签
    cnt++;                // 岛屿面积 +1
    for (int i = 0; i < 4; i++)
    {
        int nextx = x + dx[i];
        int nexty = y + dy[i];
        if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) // 越界 -- 跳过
            continue;
        dfs(grid, visited, nextx, nexty, mark);
    }
}

int main()
{
    std::ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0); 
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            cin >> grid[i][j];
        }
    }
    vector<vector<bool>> visited(n, vector<bool>(m, false)); // 标记访问过的点
    unordered_map<int, int> hash;                            // 哈希表:用于记录岛屿编号及其面积
    int mark = 2;                                            // 记录每个岛屿的编号,从2开始,因为1已经被用作陆地,避免异意
    bool isAllGrid = true;                                   // 标记是否整个地图都是陆地
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            if (grid[i][j] == 0) // 如果遇到海水,则整个地图不是全为陆地
                isAllGrid = false;
            if (!visited[i][j] && grid[i][j] == 1) // 如果节点未访问且为陆地
            {
                cnt = 0;                        // 重置岛屿面积计数
                dfs(grid, visited, i, j, mark); // 将与其链接的陆地(该岛屿)都标记上 true,并计算当前岛屿面积
                hash[mark] = cnt;               // 记录每一个岛屿的面积
                mark++;                         // 记录下一个岛屿编号
            }
        }
    }
    if (isAllGrid)
    {
        cout << n * m << endl; // 如果都是陆地,返回全面积
        return 0;              // 结束
    }
    // 计算添加一块陆地后,连接的岛屿面积之和的最大值
    int ans = 0;                    // 记录最后结果
    unordered_set<int> visitedGrid; // 标记访问过的岛屿
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            cnt = 1;             // 初始化岛屿数量,由于当前位置是陆地,所以初始值为1
            visitedGrid.clear(); // 清空已访问岛屿编号集合,准备计算新的岛屿面积之和
            if (grid[i][j] == 0)
            {
                for (int k = 0; k < 4; k++)
                {
                    int neari = i + dx[k]; // 计算相邻坐标
                    int nearj = j + dy[k];
                    if (neari < 0 || neari >= n || nearj < 0 || nearj >= m) // 如果当前位置为海水,则计算添加陆地后的岛屿面积之和
                        continue;
                    if (visitedGrid.count(grid[neari][nearj])) // 如果相邻节点已经被访问过,则跳过 -- 即添加过的岛屿不要重复添加
                        continue; 
                    // 把相邻四面的岛屿数量加起来
                    cnt += hash[grid[neari][nearj]];        // 累加相邻岛屿的面积
                    visitedGrid.insert(grid[neari][nearj]); // 标记该岛屿已经添加过
                }
            }
            ans = max(ans, cnt);
        }
    }
    cout << ans << endl;
}

 三、小结

水流问题和建造最大岛屿都是图论章节对于两大基本搜索的应用,我们需要掌握的不仅是 DFS 函数或者 BFS 函数的书写和作用,还有如何去优化处理将问题简单化。

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

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

相关文章

AI prompt(提示词)

# 好用的用于学习的AI提示词 ## 费曼学习法 请使用费曼学习法&#xff0c;用简单的语言解释&#xff08;量子力学&#xff09;是什么&#xff0c;并提供一个简单的例子来说明它如何应用 ## 帕累托法则&#xff08;80/20原则&#xff09; 将&#xff08;量子力学&#xff09;最…

Chapter 13 普通组件的注册使用

欢迎大家订阅【Vue2Vue3】入门到实践 专栏&#xff0c;开启你的 Vue 学习之旅&#xff01; 文章目录 前言一、组件创建二、局部注册三、全局注册 前言 在 Vue.js 中&#xff0c;组件是构建应用程序的基本单元。本章详细讲解了注册和使用 Vue 的普通组件的两种方式&#xff1a;…

图像分割分析效果

下面是训练集的效果, # 训练集dice: 0.9219 - iou: 0.8611 - loss: 0.0318 - mae: 0.0220 - total: 0.8915 basnet_model.evaluate(train_dataset) 损失我只用了二元交叉熵,主要比较损失的影响 上面就是模型在训练集上的效果,可见,模型在训练集上拟合的非常好,既学到了一些有用…

百万次使用的高颜值在线绘图平台ImageGP系列教程

在线平台BIC (https://www.bic.ac.cn/BIC/#/, 点击阅读原文或百度搜索皆可访问到)是 ImageGP的重构升级版&#xff0c;重构于2020年初。 该平台采用配置文件快速部署工具、生成结果或结果报告。其绘图和分析基于 R 语言(ImageGP 包, 在早期ImageGP脚本的基础上重新进行了封装&a…

flume 使用 exec 采集容器日志,转储磁盘

flume 使用 exec 采集容器日志&#xff0c;转储磁盘 在该场景下&#xff0c;docker 服务为superset&#xff0c;flume 的sources 选择 exec &#xff0c; sinks选择 file roll 。 任务配置 具体配置文件如下&#xff1a; #simple.conf: A single-node Flume configuration#…

深入理解java并发编程之aqs框架

跟synchronized 相比较&#xff0c;可重入锁ReentrankLock其实原理有什么不同&#xff1f; 所得基本原理是为了达到一个目的&#xff1b;就是让所有线程都能看到某种标记。synchronized通过在对象头中设置标记实现了这一目的&#xff0c;是一种JVM原生的锁实现方式。而Reentran…

基于深度学习 卷积神经网络resnext50的中医舌苔分类系统

项目概述 本项目旨在通过深度学习技术&#xff0c;特别是利用卷积神经网络&#xff08;Convolutional Neural Networks, CNNs&#xff09;中的ResNeXt50架构&#xff0c;实现对中医舌象图像的自动分类。该系统不仅能够识别不同的舌苔类型&#xff0c;还能够在PyQt5框架下提供一…

IBM Storwize V7000存储控制器故障节点报错574

背景&#xff1a;由于客户机房搬迁&#xff0c;需要下电迁移设备。该存储自2016年投入生产使用后&#xff0c;从未关过机&#xff0c;已正常运行七八年时间&#xff0c;期间只更换过硬盘&#xff0c;无其他硬件故障。 在GUI界面点击关闭系统后&#xff0c;大概等了40分钟&…

AIGC简化文件管理:Python自动重命名Word和PDF文件

1.背景 大家应该也有遇到&#xff0c;自己电脑有很多文件命名不合理的文件&#xff0c;比如&#xff1a;文件1、想法3 &#xff0c;当你长时间再看到这个文件的时候&#xff0c;已经很难知道文件内容。 今天我们将借助AIGC的编码能力&#xff0c;帮我们生成一个批量改文件名的…

多个路由器级联实现子网的方式

好久没写博客啦&#xff0c;最近搬家&#xff0c;换了网络环境&#xff0c;简单记录一下网络配置。 拓扑图就不画了&#xff0c;光猫 - > 华为TC7102路由 -> 华为AX2 Pro路由 -> 各种设备&#xff0c;简单表示就是这样。 原因是第一个路由是房东的&#xff0c;我希望自…

绿色能源在日常生活中的革新应用

绿色能源在日常生活中的革新应用 在当今社会&#xff0c;绿色能源已成为推动可持续发展的关键力量。它不仅代表着对环境的尊重与保护&#xff0c;更引领着生活方式的绿色转型。在我们的日常生活中&#xff0c;绿色能源正以多样化的形式&#xff0c;悄然改变着我们的生活方式。…

emlog Pro动态百分比顶部滚动条插件

插件介绍 给emlog博客顶部添加一个动态百分比顶部滚动条&#xff0c;可以选择设置图片、渐变背景样式。这个动态效果出现好久了&#xff0c;然后在js监测屏幕下拉值&#xff0c;最后用css改变图片和背景的宽度&#xff0c;从而实现动态的效果。目前支持10多种样式供选择&#…

iPhone手机清理软件:照片清理功能全解析

在数字化生活中&#xff0c;智能手机成为我们记录生活点滴的主要工具&#xff0c;尤其是iPhone&#xff0c;以其卓越的相机功能备受用户青睐。然而&#xff0c;成千上万的照片迅速堆积&#xff0c;不仅占用了大量存储空间&#xff0c;还使得设备运行缓慢。在众多解决方案中&…

软件工程-图书管理系统的概要设计

软件概要设计说明书 目录 软件概要设计说明书 一、引言 1.1 编写目的 1.2 背景 1.3 定义 1.3.1特定对象 1.3.2专业术语 1.4 参考资料 二、总体设计 2.1 需求规定 2.1.1信息要求 2.1.2功能要求 2.2 运行环境 2.3 基本概要设计和处理流程 2.4 体系结构设计 2.5 模…

Android Jetpact Lifecycle 解析

认识 Lifecycle Lifecycle 是什么 Lifecycle 是 Jetpack 组件库中的一个生命周期感知型组件。在 Lifecycle 出现之前&#xff0c;需要手动从外部宿主&#xff08;如 Activity、Fragment 或自定义宿主&#xff09;中将生命周期事件分发到功能组件内部&#xff0c;这势必会造成…

GEE数据集:加拿大卫星森林资源调查 (SBFI)-2020 年加拿大森林覆盖、干扰恢复、结构、物种、林分年龄以及 1985-2020 年林分替代干扰的信息

目录 简介 数据集后处理 数据下载链接 矢量属性 代码 代码链接 引用 许可 网址推荐 0代码在线构建地图应用 机器学习 加拿大卫星森林资源调查 (SBFI) 简介 卫星森林资源清查&#xff08;SBFI&#xff09;提供了 2020 年加拿大森林覆盖、干扰恢复、结构、物种、林分…

6.3图的遍历

图的遍历是指从某点出发,按照某种搜索方式沿着边访问图中所有节点 图的遍历算法主要有两种:广度优先,深度优先 都需要辅助数组visited[]来记录节点是否被访问过 6.3.1广度优先搜索 like层次遍历,需要辅助队列 代码实现 #include<stdio.h> #define maxnum 15 bool vi…

解决Win10版Township进度保存问题

解决Win10版Township进度保存问题 问题描述问题分析解决步骤1.WinR打开运行&#xff0c;输入regedit点击确定打开注册表2.进入注册表“计算机\HKEY_CURRENT_USER\Software\Classes\LocalSettings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Mappings”目录3.在这…

算法题之水壶问题

水壶问题 有两个水壶&#xff0c;容量分别为 x 和 y 升。水的供应是无限的。确定是否有可能使用这两个壶准确得到 target 升。 你可以&#xff1a; 装满任意一个水壶清空任意一个水壶将水从一个水壶倒入另一个水壶&#xff0c;直到接水壶已满&#xff0c;或倒水壶已空。 示…

LabVIEW制系统开发流程介绍

在开发一个LabVIEW电机控制系统时&#xff0c;尤其是涉及多种类型的电机并需实现本地与远程控制时&#xff0c;合理的开发顺序是确保项目高效完成且返工最少的关键。下面介绍如何按照系统需求分阶段开发&#xff0c;从而保障开发的速度与质量&#xff0c;减少返工的风险。 开发…