第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;
}