在 leetcode 上写 DFS 时,看到了 nettee 老哥的-DFS 框架,于是写了几道 岛屿问题 练练手
岛屿问题:
本质上就是 求图中 连通分量的个数
求图中 连通分量个数的方法(3 种):
详细题解,见 DFS & BFS & 并查集 -leetcode-200. 岛屿数量
本题 求的是 连通分量的最外侧周长
思路:
- 数据较小, [1, 100],直接 dfs 爆搜,即可
- 由图可知,如果当前是陆地,并且从当前位置 扩展的“新位置”是“水坑”或“新位置“越界,则会为周长贡献一条边;否则(即,新位置已经被遍历过了),不会为 周长贡献 边
这里使用的是 zju_ds dfs 魔板 ,当然本题也可以采用 nettee 老哥的-DFS 框架 + 带有返回值 inrt 的 DFS (见 827. 最大人工岛 代码)
class Solution {
int[] dirx = {-1, 1, 0, 0};
int[] diry = {0, 0, -1, 1};
int[][] grid;
boolean[][] visited;
int m;
int n;
int res = 0;
public int islandPerimeter(int[][] grid) {
this.grid = grid;
this.m = grid.length;
this.n = grid[0].length;
this.visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1 && !visited[i][j]) {
dfs(i, j);
}
}
}
return res;
}
public void dfs(int x, int y) {
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int xx = x + dirx[i];
int yy = y + diry[i];
if (xx < 0 || xx >= m || yy < 0 || yy >= n || grid[xx][yy] == 0) {
res++;
continue;
}
if (visited[xx][yy]) {
continue;
}
dfs(xx, yy);
}
}
}
注意: 给定的矩阵grid 的长度和宽度都不超过 50。
本题其实就是求 “最大”(顶点数最多)的连通分量
思路:
- 数据较小, [1, 50],直接经典 dfs 爆搜,即可
- 经典 DFS 求每个连通分量的个数,然后取最大值
这里使用的是 zju_ds dfs 魔板 ,当然本题也可以采用 nettee 老哥的-DFS 框架 + 带有返回值 inrt 的 DFS (见 827. 最大人工岛 代码)
class Solution {
int[] dirx = {-1, 1, 0, 0};
int[] diry = {0, 0, -1, 1};
int[][] grid;
boolean[][] visited;
int m;
int n;
int area;
int maxArea = 0;
public int maxAreaOfIsland(int[][] grid) {
this.grid = grid;
this.m = grid.length;
this.n = grid[0].length;
this.visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1 && !visited[i][j]) {
area = 1;
dfs(i, j);
maxArea = Math.max(area, maxArea);
}
}
}
return maxArea;
}
public void dfs(int x, int y) {
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int xx = x + dirx[i];
int yy = y + diry[i];
if ((xx < 0 || xx >= m || yy < 0 || yy >= n) || visited[xx][yy] || grid[xx][yy] == 0) {
continue;
}
area++;
dfs(xx, yy);
}
}
}
提示:
- n == grid.length
- n == grid[i].length
- 1 <= n <= 500
- grid[i][j] 为 0 或 1
思路:
- DFS 尝试填充每一个 “水坑”,然后取最大值。
TLE - 两次 DFS + 哈希表 + 染色
AC
DFS 尝试填充每一个 “水坑” TLE
错误做法 1:求 top2 的连通分量 WA
- 最开始想的是,类似 695. 岛屿的最大面积 求 top2 的连通分量,然后加一。后来一想,觉得不可,因为 为这两个 top2 的岛屿 添加一个“陆地”后 不一定 连通
错误做法 2:DFS 尝试填充每一个 “水坑” TLE
- 将当前海洋格子 “填海造陆”
gird[i][j] = 1 (即,一次填海造陆) ; - 然后从当前 “海洋格子” 出发
DFS 求连通分量的顶点数; - 本次
DFS 完事后,需要进行回溯(即,gird[i][j] = 0 ,将 填的海洋 还原回去,以待后面其他位置 “填海造陆” )
注意:这里不同于 “岛屿数量” & “岛屿最大面积”(只用 visited 全局初始化一次),这里每次以 (i, j) 水坑开始 dfs 之前,都要初始化 visited
但是,这样写超时了
class Solution {
int[] dirx = {-1, 1, 0, 0};
int[] diry = {0, 0, -1, 1};
int[][] grid;
boolean[][] visited;
int m;
int n;
int area;
int maxArea = 0;
public int largestIsland(int[][] grid) {
for (int[] line : grid) {
System.out.println(Arrays.toString(line));
}
this.grid = grid;
this.m = grid.length;
this.n = grid[0].length;
this.visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0) {
for (boolean[] line : visited) {
Arrays.fill(line, false);
}
System.out.println("----------------");
System.out.println("i = " + i);
System.out.println("j = " + j);
grid[i][j] = 1;
area = 1;
dfs(i, j);
grid[i][j] = 0;
System.out.println("area = " + area);
maxArea = Math.max(area, maxArea);
}
}
}
return maxArea == 0 ? m * n : maxArea;
}
public void dfs(int x, int y) {
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int xx = x + dirx[i];
int yy = y + diry[i];
if ((xx < 0 || xx >= m || yy < 0 || yy >= n) || visited[xx][yy] || grid[xx][yy] == 0) {
continue;
}
area++;
dfs(xx, yy);
}
}
}
两次 DFS + 哈希表 + 染色 AC
参考 nettee 老哥的-DFS 框架
采用两次遍历
-
第一次 DFS 遍历 “陆地”,记录每个岛屿的面积
- 这里将 已经访问过的 岛屿 标记为 “岛屿索引号” index(grid 中已经有 0、1 了,为了区分 所以从 2 开始),而不是面积
- 采用 map 记录每个岛屿序号为 index 的“面积”
-
第二次 dfs:遍历海洋格子,尝试将 “海洋格子” 和 周围的 “岛屿” 合并,取 max
- 找当前海洋格子的邻居节点,如果是两个岛屿 则合并之
采用 nettee 老哥的-DFS 框架,类似二叉树 dfs 魔板 + 哈希表 + 两次遍历 + 染色
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
class Solution {
int[] dirx = {-1, 1, 0, 0};
int[] diry = {0, 0, -1, 1};
int[][] grid;
int m;
int n;
public int largestIsland(int[][] grid) {
this.grid = grid;
this.m = grid.length;
this.n = grid[0].length;
int res = 0;
int index = 2;
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
int area = dfs(i, j, index);
res = Math.max(res, area);
map.put(index++, area);
}
}
}
System.out.println(map);
for (int[] g : grid) {
System.out.println(Arrays.toString(g));
}
if (res == 0) {
return 1;
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0) {
HashSet<Integer> set = findNeighbors(i, j);
System.out.println("set = " + set);
if (set.size() < 1) {
continue;
}
int cnt = 1;
for (Integer islandIndex : set) {
cnt += map.get(islandIndex);
}
res = Math.max(cnt, res);
}
}
}
return res;
}
public HashSet<Integer> findNeighbors(int x, int y) {
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < 4; i++) {
int xx = x + dirx[i];
int yy = y + diry[i];
if (xx < 0 || xx >= m || yy < 0 || yy >= n || grid[xx][yy] == 0) {
continue;
}
set.add(grid[xx][yy]);
}
return set;
}
public int dfs(int x, int y, int index) {
if (x < 0 || x >= m || y < 0 || y >= n) {
return 0;
}
if (grid[x][y] != 1) {
return 0;
}
grid[x][y] = index;
int area = 0;
for (int i = 0; i < 4; i++) {
int xx = x + dirx[i];
int yy = y + diry[i];
area += dfs(xx, yy, index);
}
return area + 1;
}
public static void main(String[] args) {
int[][] grid = {{1, 1}, {1, 1}};
System.out.println(new Solution().largestIsland(grid));
}
}
这题写的真费劲,太菜了
|