代码随想录算法训练营第60期第五十六天打卡
大家好,我们上次是讲解了岛屿数量和岛屿的最大面积的题目,我们其实通过这两道题目就可以大致入门图论部分了,大家也逐步开始使用dfs与bfs来解决相关的问题,我们今天继续我们的图论,我们还是使用这两种方法去解决岛屿的相关问题。
第一题对应卡码网编号为101的题目孤岛的总面积
我们开始我们今天的第一道题目,这道题目大家是不是会感觉与昨天的题目岛屿的最大面积很相似啊,其实是的的确很相似,但是我们也会看到他们的不同,我们还是先看一下题目要求:
首先题目要求我们求的是孤岛的总面积,首先我们要通过题目要求来确定什么样的岛屿叫做 孤岛,首先题目说了要求所有的单元格都不接触边缘的岛屿,这个其实就是我们的陆地是不能处在地图的边界的,这一点大家务必要注意,那么了解了这个之后其实我们就可以考虑使用我们最近的算法来解决这道题目了,我这里使用的深度优先搜索,其实这里我们可以使用一种很奇妙的方法,我们可以借助深度优先搜索去将我们靠近地图边界的陆地变成海洋,这样最后留下的陆地的总面积不就是我们题目要求求的孤岛的总面积了,我们可以尝试写一下深搜的解题代码:
#include <iostream>
#include <vector>
using namespace std;int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};void dfs(vector<vector<int>> &grid, int x, int y)
{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] == 0) continue;dfs(grid, nextx, nexty);}
}int main()
{int n, m; cin >> n >> m;vector<vector<int>> grid(n + 1, vector<int>(m + 1, 0));//读入地图for (int i = 0; i < n; ++i){for (int j = 0; j < m; ++j) cin >> grid[i][j];}//使用dfs将靠近地图边界的陆地变成海洋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 result = 0;for (int i = 0; i < n; ++i){for (int j = 0; j < m; ++j){if (grid[i][j] == 1){result++;}}}cout << result << '\n';return 0;
}
其实这道题目我们是借助了深度优先搜索排除了所有不符合题目要求的陆地,那么留下来的就是孤岛的总面积,还有我们不仅仅排除了所有地图边界上的陆地还有与它相邻的陆地,这点大家要了解,其实这道题目并不难,关键能不能想到这种思路。
第二题对应卡码网编号为102的题目沉没孤岛
这是我们今天的第二题沉默孤岛,其实这道题目的意思也很简单就是我们要将所有的孤岛变成海洋,我们上一道题就是求的是孤岛的总面积,那么这道题就是将上一题求出来的孤岛全部变成海洋,我们又有了奇妙的解法,我们其实不必考虑使用visited数组考虑,完全没必要而且这样的话还麻烦了,我们可以这样我们将靠近地图边界的陆地和与其相邻的陆地变为2,这里我们需要借助深搜来搜索所有相邻的陆地,随后我们再去遍历地图我们可以将标记为2的陆地变回1,然后没有被变成2的陆地变成0,这其实就是孤岛,最后我们再遍历一遍地图打印出来就可以了,其实思路如果想到了话就不难,如果想不到的话估计就很有难度了,我们就尝试使用深搜来解决这道题目:
#include <iostream>
#include <vector>using namespace std;
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};void dfs(vector<vector<int>> &grid, int x, int y)
{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;if (grid[nextx][nexty] == 0 || grid[nextx][nexty] == 2) continue;if (grid[nextx][nexty] == 1) dfs(grid, nextx, nexty);}
}int main()
{int n,m; cin >> n >> m;vector<vector<int>> grid(n + 1, vector<int>(m + 1, 0));for (int i = 0; i < n; ++i){for (int j = 0; j < m; ++j) cin >> grid[i][j];} //借助dfs将所有处在地图边界的陆地以及相邻的陆地标记为2for (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);}//恢复现场for (int i = 0; i < n; ++i){for (int j = 0; j < m; ++j){if (grid[i][j] == 2) grid[i][j] = 1;else if (grid[i][j] == 1) grid[i][j] = 0;}}for (int i = 0; i < n; ++i){for (int j = 0; j < m; ++j){cout << grid[i][j] << " ";}cout << '\n';}return 0;
}
这道题目其实还是思路,大家岛屿类型的问题做的多了思路自然就会多,写代码自然也会变得更快更准,这道题目与上一道题目大家要类比学习,找到思路上的不同,我们是如何使用深搜的,深搜在我们的题目里发挥的作用是什么。
第三题对应卡码网编号为103的题目水流问题
这道题目估计就有一定难度了,我们还是先看看题目要求:
题目的意思是我们需要找到所有存在于该位置的水流可以同时流到第一组边界与第二组边界的点的位置,其实我们有了前面两道题目的思路,我们是不是可以模仿一下使用逆向思维,我们其实可以从第一组边界与第二组边界同时出发开始使用dfs搜索,如果可以同时搜索到这个点就是符合要求的点,这种思路其实是很巧妙的,但是我们要注意我们的水只能从高处流向较低或者等高并且要相邻的点,如果我们要逆向考虑的话我们其实应该从高度低的点去搜索高度高的点或者是等高的点,这就是我们的思路,其实我们需要两个数组保存分别从第一组边界出发搜索和从第二组边界出发搜索的情况,有了这个思路我们就可以尝试给出本题的解题代码:
#include <iostream>
#include <vector>
using namespace std;int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
int n,m;
void dfs(const 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;
}int main()
{cin >> n >> m;vector<vector<int>> grid(n + 1, vector<int>(m + 1, 0));//读取地图for (int i = 0; i < n; ++i){for (int j = 0; j < m; ++j) cin >> grid[i][j];}vector<vector<bool>> firstborder(n + 1, vector<bool>(m + 1, false));vector<vector<bool>> secondborder(n + 1, vector<bool>(m + 1, 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 << '\n';}}}return 0;
}
题目还是有一定的难度,大家是否可以想到我们的逆向思维的思路,大家一定要把这道题目多看几遍,一定要在不看答案的情况下自己把代码完完整整地写出来才可以。
第四题对应卡码网编号为104的题目建造最大岛屿
这是我们今天的最后一道题目,我们还是先来看看题目要求:
题目的大体意思是我们可以将一格水变为陆地,这样我们去求最大的岛屿面积,当然岛屿的条件还是上下或者左右相邻,不能是斜着的,那我们就看看本题的解题思路:其实题目有一个很暴力的想法我们是不是可以考虑每一处的海洋都变为陆地然后我们搜索最大的陆地的面积,但这样其实我们会做一些无用的工作,我们不需要这样,其实我们可以考虑优化的做法,我们可以遍历地图得到各个岛屿的面积同时我们要编号,使用map存储岛屿编号与岛屿的面积,然后再遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积。这个题目难度比较大,我们应该是通过深搜给每一个岛屿都编号并且计算出面积,然后我们将编号与面积存储在一个map里面,然后接下来我们是根据陆地的位置来确定最大的岛屿的面积,我们来看看解题思路:
#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; // 越界了,直接跳过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); // 将与其链接的陆地都标记上 truegridNum[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;}
题目难度较大,大家努力理解。尤其是我们陆地的逻辑。
今日总结
我们今天先讲解这四道题目,其实难度是有的但是又不是很大,主要是大家的思路是否活跃,如果这道水流问题大家想不到逆向思维的话估计很难做出来,大家务必理解好这里的每一道题目,后面的题目都是这些题目的变式题,我们今天就分享到这里,我们明天再见!