参考资料:代码随想录
102. 沉没孤岛
给定一个由0和1组成的矩阵,在垂直和水平方向垂直方向相邻的陆地单元格组成的区域,岛屿指的是完全被水域包围的地方
现在你需要知道孤岛是不靠近岛边且被水域包围的岛屿,现在你需要沉没所有孤岛,就是将所有陆地单元格变成水域单元格。孤岛是位于矩阵内部。所有单元格都不接触边缘的岛屿。
现在有N和M,N表示行数M表示列数,之后N行表示M个数字,数字为0或者1,表示岛屿的单元格。
思路分析
思路依然是从地图周边出发,然后将周边空格相邻的陆地都做上标记,然后遍历一遍地图,把陆地改成水域就可以。
终止条件是再遍历一次地图,遇到陆地且没有做过标记的,全部改成水域就可以。
有的人可能回想定义一个visited[][],单调标记周边的陆地然后遍历 visited[][]和陆地, 然后遍历的时候进行判断,决定陆地是否变成水域。
这样做其实有点麻烦了,不用额为的空间,直接改陆地为其他特殊值作为标记就可以。
步骤一 深度搜索或者广度搜索把边上的陆地改为2,中间的陆地改为1,其他水域改为0就可以了。
步骤二 将水域中间1(陆地) 改为水域0,
步骤三 将之前标记 的2改为1陆地。
#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) {
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;
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);
}
// 步骤二、步骤三
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;
}
}
遇到的问题
int nextx = x + dir[i][0];// i不是0 1 2 3 吗?和四个方向有什么关系?
- i是循环变量,表示当前的方向索引。
- 根据
i的值,从dir数组中取出对应的位移量{x_offset, y_offset},计算下一个位置(nextx, nexty)。
具体对应关系如下:
- 当
i = 0时,dir[0]是{ -1, 0 },表示向上移动。 - 当
i = 1时,dir[1]是{ 0, -1 },表示向左移动。 - 当
i = 2时,dir[2]是{ 1, 0 },表示向下移动。 - 当
i = 3时,dir[3]是{ 0, 1 },表示向右移动。
假设当前坐标是 (x, y) = (2, 3),我们通过 dir 数组计算四个方向的下一个位置:
i | dir[i] | 计算公式 | 下一个位置 (nextx, nexty) |
|---|---|---|---|
| 0 | { -1, 0 } | (2 + (-1), 3 + 0) | (1, 3) |
| 1 | { 0, -1 } | (2 + 0, 3 + (-1)) | (2, 2) |
| 2 | { 1, 0 } | (2 + 1, 3 + 0) | (3, 3) |
| 3 | { 0, 1 } | (2 + 0, 3 + 1) | (2, 4) |
通过这种方式,我们可以遍历当前位置的四个相邻方向。
103. 水流问题
现在有一个N*M的矩阵,里面有很多单元格,每个单元格里面包含一个数值。单元格里的数值代表它的相对高度,矩阵的左边界和上边界为它的第一组边界,矩阵的右下为第二边界。矩阵会向下倾斜,但是只能从较高的点向较低的点流动。从单元格出发的水可以达到左上或者右下,这样的。我们的目标是确定那些单元格,从这些单元格出发的水可以达到第一组边界和第二组边界。

比如蓝色方块的水既能流向第一组边界,又能流向第二组边界,所有最终的答案为蓝色方块的所有坐标。
思路分析
怎么去思考这个问题,就是遍历每一个点,可以考虑用BFS或者DFS,只能能到达到第一梯队,或者第二梯队都可以。
这样做很显然会超时,时间复杂度为n * m ,因为每次搜索每个节点都要遍历一遍,都要深搜,
可以反向思考一下,从边界出发。从第一个边界出发逆流而上,把节点都遍历上。从第二个节点出发逆流而上,把节点都遍历上。
两方都遍历过的节点就是我们要求的部分,这部分既可以流向第一组边界,又可以流向第二组边界。

这样就可以求出交界节点,这样就能写出详细代码和注释。
#include <iostream>
#include <vector>
using namespace std;
int n, m;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1};
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;
}
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>> 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;;
}
}
}
新手误区
时间复杂度为O(n * m) ,然后就dfs的事件复杂度,主函数中dfs还有for循环。
这里其实有个误区因为visited[][]函数会记录已经走过的地方,走过的节点是不会再走的。
需要看传入的参数是什么,如果是传入数组 firstBorder,地图中的每一个节点需要遍历一次,无论你调用几次。同理参数传入的是 数组 secondBorder,地图中每个节点也只会遍历一次。
所以时间复杂度为O(n * m * 2) ,参数传入 firstBorder 和 secondBorder 各遍历一次。地图用每个节点就遍历了两次
// 从最上和最下行的节点出发,向高处遍历
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); // 遍历最下行,接触第二组边界
}
这里是每天回答一个问题
如果觉得对你有帮助的话
可以点一个免费的赞
1246

被折叠的 条评论
为什么被折叠?



