网格图–Day01–网格图DFS–200. 岛屿数量,695. 岛屿的最大面积,3619. 总价值可以被 K 整除的岛屿数目
今天要训练的题目类型是:【网格图DFS】,题单来自@灵艾山茶府。
适用于需要计算连通块个数、大小的题目。
部分题目做法不止一种,也可以用 BFS 或并查集解决。
DFS函数中的三步曲:判断,处理,继续DFS。
- 判断:是否越界,是否是需要DFS的格子
- 处理:根据题意处理格子
- 继续DFS:DFS四个方向,有时候可能需要收集返回值。
200. 岛屿数量
思路【我】:
- 使用visited数组,标记格子是否被访问过。
- 如果是岛屿,且没有被访问过,进去开始DFS。
- 标记已访问。
- 向右下左上四个方向探索。首先判断索引是否越界,接着判断是不是岛屿,再判断岛屿是否被访问过。条件都满足的话,DFS这个格子。
class Solution {
private final int[][] DIRS = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } };
private void dfs(char[][] grid, boolean[][] visited, int x, int y) {
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int nextX = x + DIRS[i][0];
int nextY = y + DIRS[i][1];
if (xyOK(grid, nextX, nextY) && grid[nextX][nextY] == '1' && !visited[nextX][nextY]) {
dfs(grid, visited, nextX, nextY);
}
}
}
private boolean xyOK(char[][] grid, int x, int y) {
if (x < 0 || y < 0 || x >= grid.length || y >= grid[0].length) {
return false;
}
return true;
}
public int numIslands(char[][] grid) {
int n = grid.length;
int m = grid[0].length;
int count = 0;
boolean[][] visited = new boolean[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == '1' && !visited[i][j]) {
dfs(grid, visited, i, j);
count++;
}
}
}
return count;
}
}
思路【@灵艾山茶府】:
不同之处:
- 省去标记数组,直接赋值grid=2来标记已访问。
- 进入DFS之后,再判断索引,是否是陆地,是否已访问。
class Solution {
private final int[][] DIRS = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } };
private void dfs(char[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] != '1') {
return;
}
grid[i][j] = '2';
for (int k = 0; k < 4; k++) {
int x = i + DIRS[k][0];
int y = j + DIRS[k][1];
dfs(grid, x, y);
}
}
public int numIslands(char[][] grid) {
int n = grid.length;
int m = grid[0].length;
int count = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
count++;
}
}
}
return count;
}
}
另一个版本:
区别是:先判断条件,再进DFS。这样写可以减少递归次数,降低时空复杂度。但是风险会更大,如果条件写得不够好,可能会死循环。
private void dfs(char[][] grid, int i, int j) {
grid[i][j] = '2';
for (int k = 0; k < 4; k++) {
int x = i + DIRS[k][0];
int y = j + DIRS[k][1];
if (!(x < 0 || y < 0 || x >= grid.length || y >= grid[0].length) && grid[x][y] == '1') {
dfs(grid, x, y);
}
}
}
695. 岛屿的最大面积
思路【我】:
使用全局变量area和maxArea。
- 在主函数发现新岛屿的时候,把area置0.
- 每进一个格子就area++,
- 回到主函数的时候,这个岛屿遍历完了,更新maxArea。
class Solution {
private final int[][] DIRS = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } };
private int area = 0;
private int maxArea = 0;
private void dfs(int[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] != 1) {
return;
}
area++;
grid[i][j] = 2;
for (int k = 0; k < 4; k++) {
int x = i + DIRS[k][0];
int y = j + DIRS[k][1];
dfs(grid, x, y);
}
}
public int maxAreaOfIsland(int[][] grid) {
int n = grid.length;
int m = grid[0].length;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 1) {
area = 0;
dfs(grid, i, j);
maxArea = Math.max(maxArea, area);
}
}
}
return maxArea;
}
}
思路【@灵艾山茶府】:
使用递归函数的返回值,累加面积返回到上层。
class Solution {
private final int[][] DIRS = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } };
private int dfs(int[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] != 1) {
return 0;
}
int area = 1;
grid[i][j] = 2;
for (int k = 0; k < 4; k++) {
int x = i + DIRS[k][0];
int y = j + DIRS[k][1];
// 注意这里是加等于。和当前格子相连的四个方向的格子,都是同一个岛屿。
area += dfs(grid, x, y);
}
return area;
}
public int maxAreaOfIsland(int[][] grid) {
int n = grid.length;
int m = grid[0].length;
int maxArea = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 1) {
int area = dfs(grid, i, j);
maxArea = Math.max(maxArea, area);
}
}
}
return maxArea;
}
}
3619. 总价值可以被 K 整除的岛屿数目
思路:
上一题每个格子的价值为1,现在每个格子有具体的价值。所以代码照抄。
需要改的地方:
- 不能赋值为2作为标记了,要赋值为0
- 返回值需要为long类型,观察题目要求,最大的val可以去到10的11次方。
class Solution {
private final int[][] DIRS = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } };
private long dfs(int[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == 0) {
return 0;
}
// 获取当前岛屿的价值
long val = grid[i][j];
// 赋值为0,将它变为水
grid[i][j] = 0;
for (int k = 0; k < 4; k++) {
int x = i + DIRS[k][0];
int y = j + DIRS[k][1];
// 注意这里是加等于。和当前格子相连的四个方向的格子,都是同一个岛屿。
val += dfs(grid, x, y);
}
return val;
}
public int countIslands(int[][] grid, int k) {
int n = grid.length;
int m = grid[0].length;
int count = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] != 0) {
long val = dfs(grid, i, j);
count += val % k == 0 ? 1 : 0;
}
}
}
return count;
}
}
到这里,可以总结DFS函数中的三步曲:判断,处理,继续DFS。
- 判断:是否越界,是否是需要DFS的格子
- 处理:根据题意处理格子
- 继续DFS:DFS四个方向,有时候可能需要收集返回值。