使用 DFS 解决 Flood Fill 类算法题
如何使用深度优先搜索(DFS)算法解决一系列经典的 Flood Fill 问题。内容涵盖七个 LeetCode 例题,包括图像渲染、岛屿数量、岛屿最大面积、被围绕的区域、太平洋大西洋水流问题、扫雷游戏以及衣橱整理。文章提供了每个问题的题目解析、算法原理说明及完整的 Java 代码实现,重点讲解了如何通过 DFS 遍历网格、标记访问状态以及处理不同方向的移动逻辑,帮助读者掌握此类图论问题的通用解法。

如何使用深度优先搜索(DFS)算法解决一系列经典的 Flood Fill 问题。内容涵盖七个 LeetCode 例题,包括图像渲染、岛屿数量、岛屿最大面积、被围绕的区域、太平洋大西洋水流问题、扫雷游戏以及衣橱整理。文章提供了每个问题的题目解析、算法原理说明及完整的 Java 代码实现,重点讲解了如何通过 DFS 遍历网格、标记访问状态以及处理不同方向的移动逻辑,帮助读者掌握此类图论问题的通用解法。

给定一个坐标,值为 r,从这个坐标开始向四个方位移动,将值等于 r 的全部修改为 color。
这类题目相对简单,核心在于深度优先搜索(DFS)遍历连通区域。
class Solution {
int c = 0;
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};
int m, n, r;
public int[][] floodFill(int[][] image, int sr, int sc, int color) {
c = color;
m = image.length;
n = image[0].length;
r = image[sr][sc];
image[sr][sc] = color;
dfs(image, sr, sc);
return image;
}
void dfs(int[][] image, int i, int j) {
if (image[i][j] == r) {
return;
}
for (int k = 0; k < 4; k++) {
int x = i + dx[k];
int y = j + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n && image[x][y] == r) {
image[x][y] = c;
dfs(image, x, y);
}
}
}
}
统计二维网格中岛屿的数量。
通过循环找到所有没被标记且数值为 1 的坐标,然后丢入 DFS 中去标记,每运行一次 DFS 就是一个岛屿。
class Solution {
int ret = 0, m, n;
boolean[][] vis;
public int numIslands(char[][] grid) {
m = grid.length;
n = grid[0].length;
vis = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1' && !vis[i][j]) {
vis[i][j] = true;
dfs(grid, i, j);
ret++;
}
}
}
return ret;
}
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};
void dfs(char[][] grid, int i, int j) {
for (int k = 0; k < 4; k++) {
int x = i + dx[k];
j + dy[k];
(x >= && x < m && y >= && y < n && !vis[x][y] && grid[x][y] == ) {
vis[x][y] = ;
dfs(grid, x, y);
}
}
}
}
计算每个岛屿的面积,返回最大面积。
class Solution {
int ret = 0, sum = 0, m, n;
boolean[][] vis;
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};
public int maxAreaOfIsland(int[][] grid) {
m = grid.length;
n = grid[0].length;
vis = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1 && !vis[i][j]) {
dfs(grid, i, j);
ret = Math.max(ret, sum);
sum = 0;
}
}
}
return ret;
}
void dfs(int[][] grid, int i, int j) {
vis[i][j] = true;
sum++;
for (int k = 0; k < 4; k++) {
i + dx[k];
j + dy[k];
(x >= && x < m && y >= && y < n && !vis[x][y] && grid[x][y] == ) {
dfs(grid, x, y);
}
}
}
}
在边缘位置,以及与边缘位置相连的区域 'O' 都不变,而在中间被包围的不和边界 'O' 相连的都替换为 'X'。
运用正难则反的思想,先将与边缘位置相邻的 'O' 都替换为一个标记字符 '',然后遍历整个数组,将剩余的 'O' 替换为 'X',标记字符 '' 改为 'O'。
class Solution {
boolean[][] vis;
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};
int m, n;
public void solve(char[][] board) {
m = board.length;
n = board[0].length;
vis = new boolean[m][n];
// 标记边界行
for (int i = 0; i < m; i++) {
if (board[i][0] == 'O' && !vis[i][0]) {
dfs(board, i, 0);
}
if (board[i][n - 1] == 'O' && !vis[i][n - 1]) {
dfs(board, i, n - 1);
}
}
// 标记边界列
for (int j = 0; j < n; j++) {
if (board[0][j] == 'O' && !vis[0][j]) {
dfs(board, 0, j);
}
if (board[m - 1][j] == 'O' && !vis[m - ][j]) {
dfs(board, m - , j);
}
}
( ; i < m; i++) {
( ; j < n; j++) {
(board[i][j] == ) {
board[i][j] = ;
}
(board[i][j] == ) {
board[i][j] = ;
}
}
}
}
{
vis[i][j] = ;
board[i][j] = ;
( ; k < ; k++) {
i + dx[k];
j + dy[k];
(x >= && x < m && y >= && y < n && !vis[x][y] && board[x][y] == ) {
dfs(board, x, y);
}
}
}
}
水往低处流,边界的网格可以直接流入大海。题目要求返回的是所有既可以流入太平洋又可以流入大西洋的坐标。
采取正难则反的思想,分别从太平洋和大西洋的边界出发,让往高处流。能从边界流到的高地,也就可以反过来从该高地流向边界。最后太平洋和大西洋相交的点符合条件的位置。
class Solution {
boolean[][] po, ao;
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};
int m, n;
public List<List<Integer>> pacificAtlantic(int[][] heights) {
m = heights.length;
n = heights[0].length;
po = new boolean[m][n];
ao = new boolean[m][n];
// 处理第一列与最后一列
for (int i = 0; i < m; i++) {
dfs(heights, i, 0, po);
dfs(heights, i, n - 1, ao);
}
// 处理第一行与最后一行
for (int j = 0; j < n; j++) {
dfs(heights, 0, j, po);
dfs(heights, m - 1, j, ao);
}
// 收集结果
List<List<Integer>> ret = new ArrayList<>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
(po[i][j] && ao[i][j]) {
List<Integer> tmp = <>();
tmp.add(i);
tmp.add(j);
ret.add(tmp);
}
}
}
ret;
}
{
vis[i][j] = ;
( ; k < ; k++) {
i + dx[k];
j + dy[k];
(x >= && x < m && y >= && y < n && !vis[x][y] && heights[x][y] >= heights[i][j]) {
dfs(heights, x, y, vis);
}
}
}
}
给定一个坐标后,这个坐标周围八个方位如果没有雷,就可以揭开这些方位的格子,同时这些被揭开的格子又可以向外扩张。
class Solution {
int[] dx = {0, 0, -1, 1, 1, -1, -1, 1};
int[] dy = {-1, 1, 0, 0, 1, -1, 1, -1};
int m, n, sum;
public char[][] updateBoard(char[][] board, int[] click) {
if (board[click[0]][click[1]] == 'M') {
board[click[0]][click[1]] = 'X';
return board;
}
m = board.length;
n = board[0].length;
dfs(board, click[0], click[1]);
return board;
}
void dfs(char[][] board, int i, int j) {
// 周围有地雷,该位置修改为 sum 后,直接结束递归
if (hasMine(board, i, j)) {
board[i][j] = (char) (sum + '0');
return;
}
board[i][j] = 'B';
for (int k = 0; k < ; k++) {
i + dx[k];
j + dy[k];
(x >= && x < m && y >= && y < n && board[x][y] == ) {
dfs(board, x, y);
}
}
}
{
;
sum = ;
( ; k < ; k++) {
i + dx[k];
j + dy[k];
(x >= && x < m && y >= && y < n && board[x][y] == ) {
sum++;
f = ;
}
}
f;
}
}
这道题相对来说比较好理解和简单,这里我们只需要考虑两个方位的移动。
根据指定的方位来进行 DFS 扩展。
class Solution {
boolean[][] vis;
int ret = 0, m, n, c;
int[] dx = {0, 1};
int[] dy = {1, 0};
public int wardrobeFinishing(int mm, int nn, int cnt) {
m = mm;
n = nn;
c = cnt;
vis = new boolean[m][n];
dfs(0, 0);
return ret;
}
void dfs(int i, int j) {
vis[i][j] = true;
ret++;
for (int k = 0; k < 2; k++) {
int x = i + dx[k];
int y = j + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && digit(x, y) <= c) {
dfs(x, y);
}
}
}
int digit(int i, int j) {
int sum = ;
(i > ) {
sum += i % ;
i /= ;
}
(j > ) {
sum += j % ;
j /= ;
}
sum;
}
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online