网格图--Day02--网格图DFS--面试题 16.19. 水域大小,LCS 03. 主题空间,463. 岛屿的周长
网格图–Day02–网格图DFS–面试题 16.19. 水域大小,LCS 03. 主题空间,463. 岛屿的周长
今天要训练的题目类型是:【网格图DFS】,题单来自@灵艾山茶府。
适用于需要计算连通块个数、大小的题目。
部分题目做法不止一种,也可以用 BFS 或并查集解决。
DFS函数中的三步曲:判断,处理,继续DFS。
- 判断:是否越界,是否是需要DFS的格子
- 处理:根据题意处理格子
- 继续DFS:DFS四个方向,有时候可能需要收集返回值。
面试题 16.19. 水域大小
思路:
这道题跟695. 岛屿的最大面积是一样的,只不过一个是求岛屿的面积,一个是求水的面积,逻辑完全一样。
区别:
- 这题要求八个方向。
- 要返回每个面积,而不是最大面积。
class Solution {// 八个方向private final int[][] DIRS = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 },{ 1, 1 }, { -1, -1 }, { 1, -1 }, { -1, 1 } };private int dfs(int[][] land, int i, int j) {if (i < 0 || j < 0 || i >= land.length || j >= land[0].length || land[i][j] != 0) {return 0;}// 标记为已访问(标记为陆地)land[i][j] = 1;// 当前格子水域大小为1int area = 1;for (int k = 0; k < DIRS.length; k++) {int x = i + DIRS[k][0];int y = j + DIRS[k][1];// 注意这里是加等于。area += dfs(land, x, y);}return area;}public int[] pondSizes(int[][] land) {int n = land.length;int m = land[0].length;List<Integer> list = new ArrayList<>();for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if (land[i][j] == 0) {int area = dfs(land, i, j);list.add(area);}}}// 要排序后返回int[] res = list.stream().mapToInt(Integer::intValue).toArray();Arrays.sort(res);return res;}
}
这里扩展一下将List转为int[]的方法:
// 手动,使用for循环(效率最高,因为不用创建流)
int[] arr = new int[list.size()];
int i = 0;
for (int x : list)arr[i++] = x;
Arrays.sort(arr);// 使用stream转,然后排序
int[] arr = list.stream().mapToInt(Integer::intValue).toArray();
Arrays.sort(arr);// 使用stream,一句话转完并排序
int[] arr =list.stream().sorted().mapToInt(Integer::intValue).toArray();// List<int[]> 转为int[][]
int[][] array = list.toArray(int[][]::new);// List<List<Integer>> 转为int[][]
int[][] array = list.stream().map(row -> row.stream().mapToInt(Integer::intValue).toArray()).toArray(int[][]::new);
// ps:这段代码可能会产生每行长度不同的 int[][] 数组。
// 原因是:代码会严格按照原 List<List<Integer>> 中每个子列表(row)的实际长度来创建对应的 int[] 数组。
LCS 03. 主题空间
思路【我】:
总的思路:全局布尔变量connect标记,是否与水(走廊)连接。
- 对于主函数
- 遍历不是水,且没访问过的格子。
- 从主函数进去是一个新岛,每次都要刷新,默认为false
- 如果DFS完出来,还是false的话,表明它没有与水连接,更新res。
- 对于每一个DFS,
- 如果越到水(格子里的水,或者海水(越界了)),connect赋值true,返回。
- 如果不是相同元素,返回。
- 统计面积。
- 踩坑点:对于下一层DFS,不要传
grid[i][j]
,已经被修改过了,要传same
class Solution {private final int[][] DIRS = { { 0, 1 }, { 1, 0 }, { -1, 0 }, { 0, -1 } };private boolean connect = false;private int dfs(char[][] grid, int i, int j, char same) {// 如果越到水(格子里的水,或者海水(越界了)),connect为true,返回if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0') {connect = true;return 0;}// 如果不是相同元素,返回。if (grid[i][j] != same) {return 0;}// 标记为已访问grid[i][j] = 'x';// 统计面积int area = 1;for (int k = 0; k < DIRS.length; k++) {int x = i + DIRS[k][0];int y = j + DIRS[k][1];// 踩坑点,这里不要传grid[i][j],已经被修改过了,要传samearea += dfs(grid, x, y, same);}return area;}public int largestArea(String[] grid) {int n = grid.length;int m = grid[0].length();// 转为char[]数组char[][] g = new char[n][m];int p = 0;for (String s : grid) {g[p++] = s.toCharArray();}int res = 0;for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {// 遍历不是水,且没访问过的格子if (g[i][j] != '0' && g[i][j] != 'x') {// 从这里进去是一个新岛,每次都要刷新,默认为falseconnect = false;int area = dfs(g, i, j, g[i][j]);// 如果不与水连接,那么更新resif (!connect) {res = Math.max(res, area);}}}}return res;}
}
思路:
在递归时,把connect状态,面积,包装为int[]数组返回给上一层
返回值设为长度为 2 的int[]数组。[0]表示面积,[1]表示是否与水相连。
[1]只有两个状态,0不相连,1相连
class Solution {private final int[][] DIRS = { { 0, 1 }, { 1, 0 }, { -1, 0 }, { 0, -1 } };// 返回值要有两个,[0]表示面积,[1]表示是否与走廊(水)相连,[1]只有两个状态,0不相连,1相连private int[] dfs(char[][] grid, int i, int j, char same) {// 如果越到水(格子里的水,或者海水(越界了)),connect为true,返回if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0') {return new int[] { 0, 1 };}// 如果不是相同元素,返回。if (grid[i][j] != same) {return new int[] { 0, 0 };}// 标记为已访问grid[i][j] = 'x';// 统计面积int[] res = new int[2];res[0] = 1;for (int k = 0; k < DIRS.length; k++) {int x = i + DIRS[k][0];int y = j + DIRS[k][1];// 踩坑点,这里不要传grid[i][j],已经被修改过了,要传sameint[] cur = dfs(grid, x, y, same);res[0] += cur[0];// 如果传来标记为遇到水,要向上传递。if (cur[1] == 1) {res[1] = 1;}}return res;}public int largestArea(String[] grid) {int n = grid.length;int m = grid[0].length();// 转为char[]数组char[][] g = new char[n][m];int p = 0;for (String s : grid) {g[p++] = s.toCharArray();}int res = 0;for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {// 遍历不是水,且没访问过的格子if (g[i][j] != '0' && g[i][j] != 'x') {int[] cur = dfs(g, i, j, g[i][j]);if (cur[1] == 0) {res = Math.max(res, cur[0]);}}}}return res;}
}
463. 岛屿的周长
提前看:
思路一:探索每个节点,判断它的上下左右是否为水,返回周长。
思路二:探索每个节点,统计节点个数,与节点的接触的边的数量,每接触一条边,就会损失2的周长。最终结果等于:
节点*4 - 接触边*2
思路三:直接迭代算。不用DFS
思路一:
- 探索每个节点,判断它的上下左右是否为水,返回周长。
注意!标记节点为已访问状态,不能grid[i][j] = 0;
,否则会影响。
class Solution {private final int[][] DIRS = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };private int dfs(int[][] grid, int i, int j) {// 标记已访问grid[i][j] = 2;int count = 0;// 处理本节点,当触碰到边界(边界是水),或者格子是水的时候,边长加一// 上下左右if (i - 1 < 0 || grid[i - 1][j] == 0) {count++;}if (i + 1 >= grid.length || grid[i + 1][j] == 0) {count++;}if (j - 1 < 0 || grid[i][j - 1] == 0) {count++;}if (j + 1 >= grid[0].length || grid[i][j + 1] == 0) {count++;}// 下一个节点for (int k = 0; k < DIRS.length; k++) {int x = i + DIRS[k][0];int y = j + DIRS[k][1];// x,y合法,且格子为陆地if (!(x < 0 || y < 0 || x >= grid.length || y >= grid[0].length) && grid[x][y] == 1) {count += dfs(grid, x, y);}}return count;}public int islandPerimeter(int[][] grid) {int n = grid.length;int m = grid[0].length;int res = 0;for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if (grid[i][j] == 1) {res += dfs(grid, i, j);}}}return res;}
}
思路二:
- 探索每个节点,统计节点个数,与节点的接触的边的数量。
- 每接触一条边,就会损失2的周长。
- 最终结果等于:
节点*4 - 接触边*2
注意!在判断邻居是不是陆地的时候,不能用1来判断,因为有的陆地已经被标记为2了。
class Solution {private final int[][] DIRS = { { 0, 1 }, { 1, 0 }, { -1, 0 }, { 0, -1 } };private int touch = 0;private int dfs(int[][] grid, int i, int j) {// 标记为已访问grid[i][j] = 2;// 右,下(不是水,就++);不能用1来判断,因为有的陆地已经被标记为2了if (j + 1 < grid[0].length && grid[i][j + 1] != 0) {touch++;}if (i + 1 < grid.length && grid[i + 1][j] != 0) {touch++;}// 统计面积int area = 1;for (int k = 0; k < DIRS.length; k++) {int x = i + DIRS[k][0];int y = j + DIRS[k][1];// xy合法,访问下一个没有访问过的陆地if (!(x < 0 || y < 0 || x >= grid.length || y >= grid[0].length) && grid[x][y] == 1) {area += dfs(grid, x, y);}}return area;}public int islandPerimeter(int[][] grid) {int n = grid.length;int m = grid[0].length;int land = 0;for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if (grid[i][j] == 1) {land += dfs(grid, i, j);}}}return land * 4 - touch * 2;}
}
思路三:
其实不用DFS,直接迭代,统计每个陆地格子的情况就行了。
class Solution {public int islandPerimeter(int[][] 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] == 0) {continue;}// 如果i在上边界,或者格子是水。(下同理)if (i == 0 || grid[i - 1][j] == 0) {count++;}if (i == n - 1 || grid[i + 1][j] == 0) {count++;}if (j == 0 || grid[i][j - 1] == 0) {count++;}if (j == m - 1 || grid[i][j + 1] == 0) {count++;}}}return count;}
}