文章目录
题目链接:417. 太平洋大西洋水流问题
题目解析
首先介绍一下什么是 FloodFill算法:
FloodFill 算法,也称为洪水填充 算法,指的是在区域中找到 性质相同的联通块,注意这里的联通块指的是 上下左右相邻 ,斜线不能算做相邻。该算法可以使用 深度优先搜索 和 广度优先搜索 来解决。
题目给出一个 m x n 的矩阵 heights,该岛屿的上边界和左边界与太平洋相邻,下边界和右边界与大西洋相邻,每一个单元格 heights[r][c] 表示坐标 (r, c) 上 高于海平面的高度。
岛上的雨水可以流向满足特定条件 的相邻 单元格。(这里的特定条件 是单元格的高度小于等于当前单元格的高度 ,相邻 指的是上、下、左、右相邻)
我们需要找到 "岛屿上既可以流向太平洋也可以流向大西洋的单元格"。
例如:
| + | ------ | 太平洋 | Pacific | Ocean | ------ | + |
|---|---|---|---|---|---|---|
| | | 1 | 2 | 2 | 3 | 5 |
| |
| 太平洋 | 3 | 2 | 3 | 4 |
4 |
大西洋 |
| Pacific | 2 | 4 | 5 |
3 | 1 | Atlantic |
| Ocean | 6 |
7 |
1 | 4 | 5 | Ocean |
| | | 5 |
1 | 1 | 2 | 4 | | |
| + | ------ | 大西洋 | Atlantic | Ocean | ------ | + |
返回: \[ 0, 4 , 1, 3 , 1, 4 , 2, 2 , 3, 0 , 3, 1 , 4, 0 ],其中:
- heights0, 4:5
- heights1, 3:4 和 heights1, 4:4
- heights2, 2:5
- heights3, 0:6 和 heights3, 1:7
- heights4, 0:5
方向向量
在继续之前,有必要知道我们在解决 矩阵搜索类 问题时 访问某位置上下左右四个方向 的操作。

坐标 〖i, j〗 的上下左右四个坐标是在 i 和 j 加上了 0、1、-1 :
- 上下坐标 :
〖i + (-1), j + 0〗和〖i + 1, j + 0〗; - 左右坐标 :
〖i + 0, j + (-1)〗和〖i + 0, j + 1〗。
因此需要定义两个向量坐标:dx = {0, 0, -1, 1},dy = {-1, 1, 0, 0}。
在需要访问时,通过 〖row, col〗坐标和四次循环依次访问即可。
BFS:广度优先搜索
算法原理
我们直接遍历矩阵,找到一个单元格就对其进行 bfs 看看是否既能到达太平洋也能到达大西洋。
但是,这种解法会导致许多重复路径,因此很可能会超时。
正难则反,我们反着考虑 ------ 从四个边界入手:
- 我们先扫描边界,从太平洋边界的单元格开始 bfs 找能够逆流而上 的路径,并将经过的单元格做一个标记,然后再从大西洋边界的单元格开始 bfs 找能够逆流而上的路径,同样对经过的单元格进行标记。
- 然后遍历矩阵,找 "两种标记重叠的单元格",将其坐标添加进结果中
- 矩阵遍历完毕之后,返回结果即可
标记数组
我们在进行 BFS 的时候,可能会重复进入某个方格。可以用两种方式避免重复访问:
- 在原数组上修改
- 使用标记数组
本题没有明确能否在原数组上修改,为了保险起见,我们采用第二种方式:使用布尔类型的标记数组,其大小与题目所给矩阵的大小一样。
本题比较特殊:需要两个标记数组,一个用于标记能够流向太平洋的单元格,另一个则标记大西洋的。
全局变量
我们需要在 bfs 时用到矩阵 heights,为了方便将其改为全局变量;在判断坐标合法性时需要用到矩阵的行数和列数,因此将 m 和 n 作为全局变量;用于记录结果的 ret;两个布尔类型的标记数组 pacificVisit 和 atlanticVisit 分别标记能够流向太平洋或者大西洋到单元格;方向数组dx 和 dy 辅助我们从某个位置向其上下左右四个方向访问。
java
int[][] heights;
int m, n;
List<List<Integer>> ret;
boolean[][] pacificVisit;、
boolean[][] atlanticVisit;
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};
层序遍历
我们使用一个队列实现层序遍历的操作:
- 队列存储与〖row, col〗位置相连的单元格坐标
- 当队列不为空时,一直取出队首元素获取坐标,然后根据坐标向该元素的上下左右四个方向访问查找符合条件的单元格(单元格的高度大于等于当前单元格的高度且未被访问过)
- 找到符合条件的单元格之后,入队,然后将该位置的标记更新为 true
- 当队列为空,层序遍历完毕
由于我们每次扫描矩阵边界时都要进行一次层序遍历操作,因此将该操作封装为一个方法。
代码实现
java
class Solution {
int[][] heights; // 题目所给的矩阵
int m, n; // 矩阵的行数和列数
List<List<Integer>> ret; // 记录结果的变量
boolean[][] pacificVisit; // 用于标记能够流向太平洋的方格
boolean[][] atlanticVisit;// 用于标记能够流向大西洋的方格
// 辅助访问某一位置上下左右方向的数组
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};
public List<List<Integer>> pacificAtlantic(int[][] givenHeights) {
// 初始化
heights = givenHeights;
m = heights.length;
n = heights[0].length;
ret = new ArrayList<>();
pacificVisit = new boolean[m][n];
atlanticVisit = new boolean[m][n];
// 先从太平洋和大西洋沿岸查找能够逆流而上的方格,并通过bfs标记
for (int j = 0; j < n; j++) {
bfs(pacificVisit, 0, j);
}
for (int i = 0; i < m; i++) {
bfs(pacificVisit, i, 0);
}
for (int j = 0; j < n; j++) {
bfs(atlanticVisit, m-1, j);
}
for (int i = 0; i < m; i++) {
bfs(atlanticVisit, i, n-1);
}
// 然后遍历矩阵,找出两个标记重叠的方格
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (pacificVisit[i][j] && atlanticVisit[i][j]) {
// 找到一个符合条件的单元格,更新结果
ret.add(new ArrayList<>(Arrays.asList(i, j)));
}
}
}
// 返回结果
return ret;
}
public void bfs (boolean[][] visit, int row, int col) {
// 使用队列存储与[row,col]位置相连的单元格坐标
Queue<int[]> queue = new ArrayDeque<>();
queue.offer(new int[]{row, col});
// 将位置[row,col]的访问状态修改为true
visit[row][col] = true;
// 层序遍历
while (!queue.isEmpty()) {
int[] top = queue.poll(); // 取出队首元素
row = top[0]; col = top[1]; // 获取队首元素的坐标
// 从队首元素向上下左右四个方向访问未被访问过
// 且高度大于等于当前高度的单元格
for (int k = 0; k < 4; k++) {
int x = row + dx[k], y = col + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n) {
if (heights[x][y] >= heights[row][col] && !visit[x][y]) {
queue.offer(new int[]{x, y}); // 入队
visit[x][y] = true; // 更新标记状态
}
}
}
}
}
}
DFS:深度优先搜索
算法原理
我们直接遍历矩阵,找到一个单元格就对其进行 dfs 看看是否既能到达太平洋也能到达大西洋。
但是,这种解法会导致许多重复路径,因此很可能会超时。
正难则反,我们反着考虑 ------ 从四个边界入手:
- 我们先扫描边界,从太平洋边界的单元格开始 dfs 找能够逆流而上 的路径,并将经过的单元格做一个标记,然后再从大西洋边界的单元格开始 dfs 找能够逆流而上的路径,同样对经过的单元格进行标记。
- 然后遍历矩阵,找 "两种标记重叠的单元格",将其坐标添加进结果中
- 矩阵遍历完毕之后,返回结果即可
全局变量
我们需要在 bfs 时用到矩阵 heights,为了方便将其改为全局变量;在判断坐标合法性时需要用到矩阵的行数和列数,因此将 m 和 n 作为全局变量;用于记录结果的 ret;两个布尔类型的标记数组 pacificVisit 和 atlanticVisit 分别标记能够流向太平洋或者大西洋到单元格;方向数组dx 和 dy 辅助我们从某个位置向其上下左右四个方向访问。
java
int[][] heights;
int m, n;
List<List<Integer>> ret;
boolean[][] pacificVisit;、
boolean[][] atlanticVisit;
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};
dfs 函数
函数头
本题的 dfs 函数的任务是:从指定位置〖row, col〗的上、下、左、右四个方向查找满足条件(未被标记过 且 单元格的高度大于等于当前单元格的高度)的单元格并做标记,这里的标记数组作为一个参数,返回值是 void。
java
void dfs (boolean[][] visit, int row, int col);
函数体
我们进入 dfs 的第一步是先将位置〖row, col〗的标记更新为 true,然后向该位置的上下左右四个方向搜索满足条件的位置并递归。
代码实现
java
class Solution {
int[][] heights;
int m, n;
List<List<Integer>> ret;
boolean[][] pacificVisit;
boolean[][] atlanticVisit;
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};
public List<List<Integer>> pacificAtlantic(int[][] givenHeights) {
// 初始化
heights = givenHeights;
m = heights.length;
n = heights[0].length;
ret = new ArrayList<>();
pacificVisit = new boolean[m][n];
atlanticVisit = new boolean[m][n];
// 先搜索太平洋和大西洋沿岸,将所有能逆流而上的方格通过dfs标记
for (int j = 0; j < n; j++) {
dfs(pacificVisit, 0, j);
}
for (int i = 0; i < m; i++) {
dfs(pacificVisit, i, 0);
}
for (int j = 0; j < n; j++) {
dfs(atlanticVisit, m-1, j);
}
for (int i = 0; i < m; i++) {
dfs(atlanticVisit, i, n-1);
}
// 然后遍历整个矩阵,找出两个标记重叠的方格
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (pacificVisit[i][j] && atlanticVisit[i][j]) {
ret.add(new ArrayList<>(Arrays.asList(i, j)));
}
}
}
return ret;
}
public void dfs (boolean[][] visit, int row, int col) {
visit[row][col] = true;
for (int k = 0; k < 4; k++) {
int x = row + dx[k], y = col + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n) {
if (heights[x][y] >= heights[row][col] && !visit[x][y]) {
dfs(visit, x, y);
}
}
}
}
}
文章到这里就告一段落了,若有错误请尽管指出~
完