Studying-代码随想录训练营day52| 101.孤岛的总面积、102沉没孤岛、103.水流问题、104.建造最大岛屿

news2024/12/29 11:09:02

第52天,图论part03,岛屿问题继续!!💪(ง •_•)ง,编程语言:C++

目录

101.孤岛的总面积

102沉没孤岛 

103.水流问题 

104.建造最大岛屿 


101.孤岛的总面积

文档讲解:手撕孤岛的总面积

题目:101. 孤岛的总面积 (kamacoder.com) 

学习:本题在岛屿面积问题的基础上,扩展到找孤岛的面积。首先我们要明确孤岛的含义,孤岛是不靠边的岛屿,因此如题例,只有中间的1才是孤岛。

因此我们可以采取,从周边找到陆地,然后通过dfs或者bfs的搜索方式,将周边靠陆地且相邻的陆地都变成海洋,这样剩下的陆地就都是孤岛了,再重新遍历即可地图统计还剩下的陆地即可。

代码:

#include <iostream>
#include <vector>
using namespace std;

int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1}; // 保存四个方向
void dfs(vector<vector<int>>& grid, int x, int y) { //dfs
    grid[x][y] = 0; //陆地变为海洋
    for (int i = 0; i < 4; i++) { // 向四个方向遍历
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; //越界
        if (grid[nextx][nexty] == 1) {
            dfs (grid, nextx, nexty); //找寻相邻土地
        }
    }
    return;
}


int main() {
    int n, m;
    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];
        }
    }
    //找第一列和最后一列
    for (int i = 0; i < n; i++) {
        if (grid[i][0] == 1) dfs(grid, i, 0); //把相邻土地变为海洋
        if (grid[i][m - 1] == 1) dfs(grid, i, m - 1); //把相邻土地变为海洋
    }
    //找第一行和最后一行
    for (int j = 0; j < m; j++) {
        if (grid[0][j] == 1) dfs(grid, 0, j); //把相邻土地变为海洋
        if (grid[n - 1][j] == 1) dfs(grid, n - 1, j); //把相邻土地变为海洋
    }
    
    int count = 0;//统计孤岛面积
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (grid[i][j] == 1) count++;
        }
    }
    cout << count << endl;
    return 0;
}

102沉没孤岛 

文档讲解:手撕沉没孤岛

题目: 102. 沉没孤岛 (kamacoder.com)

学习:本题和上一题正好相反,上一题是把周边的陆地沉没,而本题是把孤岛进行沉没。但其实本质都是一样的,我们最后都是要找到孤岛的位置,只不过上一题是要求我们把孤岛的面积算出来,而本题则是把孤岛的位置置为0。

因此本题的解题办法实际上和上一题是一样的,我们不把周边的陆地沉没,而是改成一个别的特殊的值,例如“2”。之后我们再遍历地图,“1”的位置就是孤岛的位置,需要置为“0",而”2“的位置就是周边的陆地,我们再改为”1“即可。

代码:dfs

#include <iostream>
#include <vector>
using namespace std;

int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0}; //四个方向
//1.确定返回参数和参数列表
void dfs(vector<vector<int>>& grid, int x, int y) { //dfs方法,这个地方grid不能用const
    //处理当前节点
    grid[x][y] = 2;
    for (int i = 0; i < 4; i++) { // 向四个方向遍历
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; //越界
        // 2.确定终止条件,不符合条件,不进入递归
        if (grid[nextx][nexty] == 0 || grid[nextx][nexty] == 2) continue;
        dfs (grid, nextx, nexty);
    }
    return;
}


int main() {
    int n, m;
    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];
        }
    }
    
    //第一列和最后一列
    for (int i = 0; i < n; i++) {
        if (grid[i][0] == 1) dfs(grid, i, 0);
        if (grid[i][m - 1] == 1) dfs(grid, i, m - 1);
    }
    //第一行和最后一行
    for (int j = 0; j < m; j++) {
        if (grid[0][j] == 1) dfs(grid, 0, j);
        if (grid[n - 1][j] == 1) dfs(grid, n - 1, j);
    }
    //将孤岛沉没,周边的陆地变为1
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (grid[i][j] == 1) grid[i][j] = 0;
            if (grid[i][j] == 2) grid[i][j] = 1;
        }
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cout << grid[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

103.水流问题 

文档讲解:手撕水流问题

题目:103. 水流问题 (kamacoder.com)

学习:本题最直接办法是遍历所有的节点,然后判断每个节点是否能够到达第一组边界和第二组边界。遍历方法,可以使用dfs,也可以使用bfs的方式。

代码:暴力超时

#include <iostream>
#include <vector>
using namespace std;
int n, m;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1};

// 从 x,y 出发 把可以走的地方都标记上
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y) {
    if (visited[x][y]) return;
    visited[x][y] = true;
    for (int i = 0; i < 4; i++) {
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) continue;
        if (grid[x][y] < grid[nextx][nexty]) continue; // 高度不合适
        dfs (grid, visited, nextx, nexty);
    }
    return;
}
bool isResult(vector<vector<int>>& grid, int x, int y) {
    vector<vector<bool>> visited(n, vector<bool>(m, false));
    // 深搜,将x,y出发 能到的节点都标记上。
    dfs(grid, visited, x, y);
    bool isFirst = false;
    bool isSecond = false;
    // 以下就是判断x,y出发,是否到达第一组边界和第二组边界
    // 第一边界的上边
    for (int j = 0; j < m; j++) {
        if (visited[0][j]) {
            isFirst = true;
            break;
        }
    }
    // 第一边界的左边
    for (int i = 0; i < n; i++) {
        if (visited[i][0]) {
            isFirst = true;
            break;
        }
    }
    // 第二边界右边
    for (int j = 0; j < m; j++) {
        if (visited[n - 1][j]) {
            isSecond = true;
            break;
        }
    }
    // 第二边界下边
    for (int i = 0; i < n; i++) {
        if (visited[i][m - 1]) {
            isSecond = true;
            break;
        }
    }
    if (isFirst && isSecond) return true;
    return false;
}
int main() {
    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];
        }
    }
    // 遍历每一个点,看是否能同时到达第一组边界和第二组边界
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (isResult(grid, i, j)) {
                cout << i << " " << j << endl;
            }
        }
    }
}

上述方法的整体时间复杂度为O(m^2 * n^2) ,这是一个四次方的时间复杂度,因此时间复杂度极高。

我们可以在理解题意的基础上进行优化,我们可以思考从第一组边界上的节点逆流而上,将遍历过的节点标记上(这些是能够到达第一个组边界的),同样从第二组边界的边上节点逆流而上,将遍历过的节点也标记上(这些是能够到达第二组边界的)。然后两方都标记过的节点就是既可以流到第一组边界又可以流到第二组边界。

代码:采用这种方法复杂度是2*n*m + n*m。因为调用dfs函数,只要参数传入的是数组 firstBorder,那么地图中每一个节点其实就遍历一次,无论你调用多少次。同理secondBoader也是一样的。

#include <iostream>
#include <vector>
using namespace std;

int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
//1.确定返回值和参数列表
void dfs(const vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y) { //dfs
    //2.确定终止条件
    if (visited[x][y]) return;
    
    visited[x][y] = true; 
    
    for(int i = 0; i < 4; i++) { //从四个方向进行寻找
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; //越界
        //3.处理当前遍历的路径
        if (grid[x][y] <= grid[nextx][nexty]) { //逆流而上,大于或者等于
            dfs(grid, visited, nextx, nexty);
        }
    }
    return;
}

int main() {
    int n, m;
    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;
            }
        }
    }
    return 0;
}

104.建造最大岛屿 

文档讲解:手撕建造最大岛屿

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

学习:本题的暴力解法,是尝试把地图的每一个0改成1,然后去搜索地图中的最大的岛屿面积。计算地图的最大面积是n*n,然后遍历地图改1也是n*n,因此最后的时间复杂度是n^4。

显然上述的时间复杂度很高,因此需要进行优化。

其实每次深搜遍历计算最大岛屿面积,都进行了很多重复的工作。我们只需要用一次深搜把每个岛屿的面积记录下来就好。因此我们遍历的顺序应该是:

第一步:一次遍历地图,得出各个岛屿的面积,并做编号记录。可以使用map记录,key为岛屿编号,value为岛屿面积。

第二步:再遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出选一个0变成1之后的最大面积。

最后可以得到代码如下,整体的时间复杂度是n*n + n*n。

代码:

#include <iostream>
#include <vector>
#include <unordered_set>
#include <unordered_map>
using namespace std;
int n, m;
int count;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y, int mark) {
    if (visited[x][y] || grid[x][y] == 0) return; // 终止条件:访问过的节点 或者 遇到海水
    visited[x][y] = true; // 标记访问过
    grid[x][y] = mark; // 给陆地标记新标签
    count++;
    for (int i = 0; i < 4; i++) {
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) continue;  // 越界了,这里能够使用n和m是因为,n和m是全局变量
        dfs(grid, visited, nextx, nexty, mark);
    }
}

int main() {
    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> gridNum;
    int mark = 2; // 记录每个岛屿的编号
    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) {
                count = 0;
                dfs(grid, visited, i, j, mark); // 将与其链接的陆地都标记上 true
                gridNum[mark] = count; // 记录每一个岛屿的面积
                mark++; // 记录下一个岛屿编号
            }
        }
    }
    if (isAllGrid) { //处理特殊情况
        cout << n * m << endl; // 如果都是陆地,返回全面积
        return 0; // 结束程序
    }
    // 以下逻辑是根据添加陆地的位置,计算周边岛屿面积之和
    int result = 0; // 记录最后结果
    unordered_set<int> visitedGrid; // 标记访问过的岛屿
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            count = 1; // 记录连接之后的岛屿数量
            visitedGrid.clear(); // 每次使用时,清空
            if (grid[i][j] == 0) {
                for (int k = 0; k < 4; k++) {
                    int neari = i + dir[k][1]; // 计算相邻坐标
                    int nearj = j + dir[k][0];
                    if (neari < 0 || neari >= n || nearj < 0 || nearj >= m) continue;
                    if (visitedGrid.count(grid[neari][nearj])) continue; // 添加过的岛屿不要重复添加
                    // 把相邻四面的岛屿数量加起来
                    count += gridNum[grid[neari][nearj]];
                    visitedGrid.insert(grid[neari][nearj]); // 标记该岛屿已经添加过
                }
            }
            result = max(result, count);
        }
    }
    cout << result << endl;
}

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

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

相关文章

昇思25天学习打卡营第XX天|SSD目标检测

感觉目标检测还是yolo相对最火&#xff1f;ssd有点老了可以更新下 SSD算法数学描述 SSD算法使用卷积神经网络&#xff08;CNN&#xff09;进行特征提取&#xff0c;并通过多尺度的特征图进行目标检测。设 ( C ) 为CNN输出的特征层数量&#xff0c;( F_i ) 为第 ( i ) 层特征…

【Postman的接口测试工具介绍】

🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步! 👉Postman接口.👋 👉Postman是一款常用的API开发测试工具,它提…

Harmony学习(四)(应用程序框架基础)

1.应该程序框架基础 多Module设计机制 模块化开发&#xff1a;一个应用多个功能&#xff0c;每个功能可作为一个模块&#xff0c;Module中可以包含源代码、资源文件、第三方库、配置文件等&#xff0c;每一个Module可以独立编译&#xff0c;实现特定的功能支持多设备&#xf…

jdk的版本匹配 Usage of ApI documented as @since 11+

IDEA 解决 Usage of API documented as since XX 的方法 如下所示&#xff0c;代码已经报错提示。 这个问题的原因是IDEA 设置的jdk Language level 语法级别太低&#xff0c;不适配代码导致的&#xff0c;只要在项目结构中将语法级别调相应的级别就可以了。具体解决思路见下图…

vue-创建自己的CLI脚手架

1.自定义命令和入口配置 首先创建一个文件夹&#xff0c;然后npm init -y生成package.json文件 添加bin命令配置入口文件 新建lib/index.js文件 然后在控制台npm link 建立软连接 、然后执行felix-cli 就可以输出代码 2.查看当前版本号命令 安装 commander npm i commander…

系统架构设计师 - 企业信息化战略与实施

企业信息化战略与实施 企业信息化战略与实施信息与信息化的概念信息的定义信息的特点信息化的概念信息化对组织的意义 信息系统生命周期 ★立项阶段开发阶段运维阶段消亡阶段 信息系统战略规划 ★ ★ ★政府信息化与电子政务 ★企业信息化与电子商务 ★ ★ ★企业资源计划企业资…

2024年让短片制作不再难,4款剪辑软件助你一臂之力!

在这个短视频流行的时代&#xff0c;每一个创意都值得被展现&#xff0c;每一份热情都值得被激发。你是不是也曾经想过&#xff0c;用镜头来讲述你的故事&#xff0c;用剪辑来展示你的才华&#xff1f;今天&#xff0c;我们一起来探索2024年制作高质量短片的秘密武器——4款强大…

gitignore文件设置,git提交时忽略部分文件

在git提交时&#xff0c;出现了非常多无用的文件&#xff0c;包括.idea、.iml文件等等&#xff0c;使得commit变得麻烦&#xff0c;要自己在勾选框中点击半天。 右键单击项目名&#xff0c;选择New 选择File,命名为.gitignore&#xff08;注意&#xff1a;开头符号是英文.&…

文件描述符(fileno)及文件系统

fileno: #include <stdio.h> main() {FILE *fp;int fd;fp fopen("/etc/passwd", "r");fd fileno(fp);printf("fd %d\n", fd);fclose(fp); } 一&#xff0e;fileno()函数-CSDN博客https://blog.csdn.net/TuxedoLinux/article/detai…

利用开源可视化报表工具进入流程化办公!

很多客户朋友都希望能实现流程化办公&#xff0c;因为只有这样才能帮助企业顺利降本、增效、提质&#xff0c;利用好企业内部数据资源&#xff0c;打破信息孤岛壁垒&#xff0c;实现高效发展。低代码技术平台、开源可视化报表工具优势功能特点多&#xff0c;是提质高效的办公利…

日本软文发稿:日本主流发稿媒体有哪些?

日本软文发稿&#xff1a;日本主流发稿媒体有哪些 在日本发布软文时&#xff0c;选择合适的主流媒体进行推广是非常关键的。以下是一些在日本广受欢迎、影响力较大的媒体推荐&#xff08;排列不区分媒体排名顺序&#xff09;&#xff1a; 1. 朝日新闻 (Asahi Shimbun) 朝日新…

ChildLife童年时光创始人Murray Clarke亮相CBME并解析技术创新

2024年7月17日至19日&#xff0c;全球知名的孕婴童产品展览会——CBME国际孕婴童展在上海盛大开幕。作为这一领域最具影响力的展会之一&#xff0c;CBME吸引了众多国际知名品牌前来参展。美国知名儿童营养品牌ChildLife童年时光也携其重磅新品“液体钙小绿钙”亮相本次展会。Ch…

【测试架构师修炼之道】读书笔记

六大质量属性 效率性能 测试类型&#xff1a;六种-XX属性转化为XX测试 产品测试车轮图 一个软件测试者要从哪些方面(测试类型)用哪些方法(测试方法)去测试产品(质量属性)的关系图 全面性与深度 稳定性测试&#xff1a;多并复异 性能测试&#xff1a; 系统能够正确处理新业…

格式化的硬盘怎么恢复数据?格式化数据恢复的7个小妙招,助你快速恢复文件

硬盘格式化不仅可以提升计算机性能、释放空间&#xff0c;还可修复部分错误。通常&#xff0c;在进行硬盘格式化前&#xff0c;系统会发出数据将被删除的警告。然而&#xff0c;有时即使有警告&#xff0c;也可能不慎格式化硬盘导致重要文件丢失。在这种情况下&#xff0c;您需…

微软GraphRAG,开启智能检索新篇章

©作者|YXFFF 来源|神州问学 1. 引言 检索增强生成&#xff08;RAG&#xff09;是一种根据用户的查询语句搜索信息&#xff0c;并以搜索结果为 AI 参考从而生成回答。这项技术是多数基于 LLM 工具的重要组成部分&#xff0c;而多数的 RAG 都采用向量相似性作为搜索的技术。…

【过题记录】 8.2 hddx

飞行棋 关于这一题 我在考场上手莫了n2和n3的情况 发现一点规律&#xff0c;大力猜想蒙了一个结论 结果蒙对了… 关于正确做法&#xff0c;发现零号点和其他几个点是不一样的。 因为对于0而言&#xff0c;他没有赠送的情况(只要摇到n就直接胜利) 因此0和其他点要分开讨论 对于…

1.大语言模型如何从专才走向通才2.GPT4的核心是可以写并执行代码,还可以接受文件读取并应用于代码中(比如中文字资料包),完全是个工程师了

1.大语言模型如何从专才&#xff08;机器翻译&#xff09;走向通才2.GPT4的核心是可以写并执行代码,还可以接受文件读取并应用于代码中&#xff08;比如中文字资料包&#xff09;&#xff0c;完全是个工程师了 总纲&#xff1a;大语言模型就是做文字接龙游戏! 大模型为了得到…

Linux 应急响应靶场练习 1

靶场在知攻善防实验室公众号中可以获取 前景需要&#xff1a;小王急匆匆地找到小张&#xff0c;小王说"李哥&#xff0c;我dev服务器被黑了",快救救我&#xff01;&#xff01; 挑战内容&#xff1a; &#xff08;1&#xff09;黑客的IP地址 &#xff08;2&#xff0…

【香橙派系列教程】(六)嵌入式SQLite数据库

【六】嵌入式SQLite数据库 文章目录 【六】嵌入式SQLite数据库1.简介2.SQLite数据库安装3.SQLite命令用法1.创建数据库2.创建和查看表格3.插入查看数据&#xff08;记录&#xff09;4.删除更改数据&#xff08;记录&#xff09; 4.SQLite编程操作1.打开/创建数据库的C接口2.创建…

C# Unity 面向对象补全计划 之 接口

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 本系列旨在通过补全学习之后&#xff0c;给出任意类图都能实现并做到逻辑上严丝合缝 1.接口 在 C# 中&#xff0c;接口&#xff08;interface&#xff09;是一种定义…