先明确一下岛屿问题中的网格结构是如何定义的,网格问题是由 m×n 个小方格组成一个网格,每个小方格与其上下左右四个方格认为是相邻的,要在这样的网格上进行某种搜索。
岛屿问题是一类典型的网格问题。每个格子中的数字可能是 0 或者 1。我们把数字为 0 的格子看成海洋格子,数字为 1 的格子看成陆地格子,这样相邻的陆地格子就连接成一个岛屿。
void traverse(TreeNode root) {
// 判断 base case
if (root == null) {
return;
}
// 访问两个相邻结点:左子结点、右子结点
traverse(root.left);
traverse(root.right);
}
可以看到,二叉树的 DFS 有两个要素:「访问相邻结点」和「判断 base case」。
对于网格上的 DFS,我们完全可以参考二叉树的 DFS,写出网格 DFS 的两个要素:
void dfs(int[][] grid, int r, int c) {
// 判断 base case
// 如果坐标 (r, c) 超出了网格范围,直接返回
if (!inArea(grid, r, c)) {
return;
}
// 访问上、下、左、右四个相邻结点
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {
return r >= 0&& r < grid.length
&& c >= 0 && c < grid[0].length;
}
如何避免重复遍历:
在框架代码中加入避免重复遍历的语句:
void dfs(int[][] grid, int r, int c) {
// 判断 base case
if (!inArea(grid, r, c)) {
return;
}
// 如果这个格子不是岛屿,直接返回
if (grid[r][c] != 1) {
return;
}
grid[r][c] = 2; // 将格子标记为「已遍历过」
// 访问上、下、左、右四个相邻结点
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {
return 0 <= r && r < grid.length
&& 0 <= c && c < grid[0].length;
}
这样,我们就得到了一个岛屿问题、乃至各种网格问题的通用 DFS 遍历方法。以下所讲的几个例题,其实都只需要在 DFS 遍历框架上稍加修改而已。
PS:
class Solution {
private int res;
public int numIslands(char[][] grid) {
res = 0;
for (int i = 0; i < grid.length; i ++) {
for (int j = 0; j < grid[0].length; j ++) {
//每次dfs会将所有相邻的岛屿标记为2,所有每次遍历到1就代表新的岛屿
if (grid[i][j] == '1') {
dfs(grid, i, j);
res ++;
}
}
}
return res;
}
private void dfs(char[][] grid, int row, int col) {
//终止条件
if (row >= grid.length || col >= grid[0].length || row < 0 || col < 0) {
return;
}
//避免重复遍历
if (grid[row][col] != '1') {
return;
}
//标记已遍历过节点
grid[row][col] = '2';
//继续遍历上下左右四个方向
dfs(grid, row - 1, col);
dfs(grid, row + 1, col);
dfs(grid, row, col - 1);
dfs(grid, row, col + 1);
}
}
主循环和思路一类似,不同点是在于搜索某岛屿边界的方法不同。
class Solution {
public int numIslands(char[][] grid) {
int res = 0;
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[0].length; j++) {
//每次bfs会将所有相邻的岛屿标记为2,所有每次遍历到1就代表新的岛屿
if(grid[i][j] == '1'){
bfs(grid, i, j);
res++;
}
}
}
return res;
}
private void bfs(char[][] grid, int i, int j){
Queue<int[]> list = new LinkedList<>();
//将每个节点坐标加入到list中
list.add(new int[] { i, j });
while(!list.isEmpty()){
int[] cur = list.remove();
i = cur[0]; j = cur[1];
//未遍历过且为陆地节点,将所有相邻的岛屿标记为2,所有每次遍历到1就代表新的岛屿
if(i >= 0 && i < grid.length && j >= 0 && j < grid[0].length && grid[i][j] == '1') {
//标记已遍历过节点
grid[i][j] = '2';
list.add(new int[] { i + 1, j });
list.add(new int[] { i - 1, j });
list.add(new int[] { i, j + 1 });
list.add(new int[] { i, j - 1 });
}
}
}
}
class Solution {
private int[][] directions = new int[][]{{0,1},{0,-1},{-1,0},{1,0}};
public int numDistinctIslands(int[][] grid) {
Set<List<Integer>> set = new HashSet<>();
if (null == grid|| grid.length == 0){
return 0;
}
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
if (grid[i][j] == 1) {
List<Integer> result = new ArrayList<>();
dfs(grid, result, i, j, i, j);
if (!result.isEmpty()) {
set.add(result);
}
}
}
}
return set.size();
}
private void dfs(int[][] grid,List<Integer> result,int x,int y,int gapX,int gapY) {
if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length || grid[x][y] != 1) {
return;
}
//遍历过的点都置为2
grid[x][y] = 2;
//每一个坐标都减去第一个坐标的值,相当于的岛屿都移动到以原点(0,0)为起点,得到了岛屿的相对位置,存入list中
result.add(x - gapX);
result.add(y - gapY);
//四个方向, DFS
for (int[] dir : directions) {
int newX = x + dir[0];
int newY = y + dir[1];
dfs(grid, result, newX, newY, gapX, gapY);
}
}
}
class Solution {
private int[][] directions = new int[][]{{0,1},{0,-1},{-1,0},{1,0}};
public int numDistinctIslands(int[][] grid) {
Set<List<Integer>> set = new HashSet<>();
if (null == grid|| grid.length == 0){
return 0;
}
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
if (grid[i][j] == 1) {
List<Integer> result = new ArrayList<>();
bfs(grid, result, i, j, i, j);
if (!result.isEmpty()) {
set.add(result);
}
}
}
}
return set.size();
}
public void bfs(int[][] grid, List<Integer> result, int x, int y, int gapX, int gapY) {
Queue<int[]> queue = new LinkedList<>();
//将每个节点坐标加入到list中
queue.offer(new int[] {x, y});
//遍历过的点都置为2
grid[x][y] = 2;
result.add(x - gapX);
result.add(y - gapY);
while(!queue.isEmpty()){
int[] cur = queue.poll();
for (int[] dir : directions) {
int newX = cur[0] + dir[0];
int newY = cur[1] + dir[1];
if (newX >= 0 && newX < grid.length && newY >= 0 && newY < grid[0].length && grid[newX][newY] == 1) {
grid[newX][newY] = 2;
result.add(newX - gapX);
result.add(newY - gapY);
queue.offer(new int[] {newX, newY});
}
}
}
}
private void dfs(int[][] grid,List<Integer> result,int x,int y,int gapX,int gapY)
{
if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length || grid[x][y] != 1) {
return;
}
//遍历过的点都置为2
grid[x][y]=2;
//每一个坐标都减去第一个坐标的值,相当于的岛屿都移动到以原点(0,0)为起点,得到了岛屿的相对位置,存入list中
result.add(x - gapX);
result.add(y - gapY);
//四个方向, DFS
for (int[] dir : directions) {
int newX = x + dir[0];
int newY = y + dir[1];
dfs(grid, result, newX, newY, gapX, gapY);
}
}
}
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
//每次dfs会将所有相邻的岛屿标记为2,并且加上相邻节点,计算岛屿面积
if (grid[i][j] == 1) {
int a = dfs(grid, i, j);
res = Math.max(res, a);
}
}
}
return res;
}
public int dfs(int[][] grid, int r, int c) {
//终止条件
if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length) {
return 0;
}
//避免重复遍历
if (grid[r][c] != 1) {
return 0;
}
//标记已遍历过节点
grid[r][c] = 2;
//继续遍历上下左右四个方向,并加上相邻的他们,求最大面积
return 1
+ dfs(grid, r - 1, c)
+ dfs(grid, r + 1, c)
+ dfs(grid, r, c - 1)
+ dfs(grid, r, c + 1);
}
}
class Solution {
private int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public int maxAreaOfIsland(int[][] grid) {
int res = 0;
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[0].length; j++) {
//每次bfs会将所有相邻的岛屿标记为2,所有每次遍历到1就代表新的岛屿
if(grid[i][j] == 1){
res = Math.max(res, bfs(grid, i, j));
}
}
}
return res;
}
public int bfs(int[][] grid, int i, int j) {
Queue<int[]> queue = new LinkedList<>();
//将每个节点坐标加入到list中
queue.offer(new int[] { i, j });
grid[i][j] = 2;
int ans = 1;
while(!queue.isEmpty()){
int[] cur = queue.poll();
//未遍历过且为陆地节点,将所有相邻的岛屿标记为2,所有每次遍历到1就代表新的岛屿
//需要加上 上下左右每个节点的有效值,所以需要每个点都判断
for (int[] dir : directions) {
int newX = cur[0] + dir[0];
int newY = cur[1] + dir[1];
if (newX >= 0 && newX < grid.length && newY >= 0 && newY < grid[0].length && grid[newX][newY] == 1) {
grid[newX][newY] = 2;
ans++;
queue.offer(new int[] {newX, newY});
}
}
}
return ans;
}
}
class Solution {
public int islandPerimeter(int[][] grid) {
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
// 题目限制只有一个岛屿,计算一个即可
if (grid[i][j] == 1) {
return dfs(grid, i, j);
}
}
}
return 0;
}
public int dfs(int[][] grid, int r, int c) {
//坐标 (r, c) 超出网格范围」返回,对应一条黄色的边
if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length) {
return 1;
}
//当前格子是海洋格子返回,对应一条蓝色的边
if (grid[r][c] == 0) {
return 1;
}
//当前格子是已遍历的陆地格子返回,和周长没关系
if (grid[r][c] == 2) {
return 0;
}
//标记已遍历过节点
grid[r][c] = 2;
//继续遍历上下左右四个方向,并加上相邻的他们,求最大面积
return dfs(grid, r - 1, c)
+ dfs(grid, r + 1, c)
+ dfs(grid, r, c - 1)
+ dfs(grid, r, c + 1);
}
}
class Solution {
private int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public int islandPerimeter(int[][] grid) {
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
// 题目限制只有一个岛屿,计算一个即可
if (grid[i][j] == 1) {
return bfs(grid, i, j);
}
}
}
return 0;
}
public int bfs(int[][] grid, int i, int j) {
Queue<int[]> queue = new LinkedList<>();
//将每个节点坐标加入到list中
queue.offer(new int[] {i, j });
grid[i][j] = 2;
int ans = 0; //先默认边长为4,接着搜索其周边的四个方向:
while(!queue.isEmpty()){
int[] cur = queue.poll();
for (int[] dir : directions) {
int newX = cur[0] + dir[0];
int newY = cur[1] + dir[1];
//坐标 (r, c) 超出网格范围」返回,对应一条黄色的边
if (newX < 0 || newX >= grid.length || newY < 0 || newY >= grid[0].length) {
ans++;
}
//当前格子是海洋格子返回,对应一条蓝色的边
else if (newX >= 0 && newX < grid.length && newY >= 0 && newY < grid[0].length && grid[newX][newY] == 0) {
ans++;
}
//遍历的新的陆地格子,加入队列
if (newX >= 0 && newX < grid.length && newY >= 0 && newY < grid[0].length && grid[newX][newY] == 1) {
grid[newX][newY] = 2;
queue.offer(new int[] {newX, newY});
} else {
//当前格子是已遍历的陆地格子返回,和周长没关系
continue;
}
}
}
return ans;
}
class Solution {
public int islandPerimeter(int[][] grid) {
int[][] directions = new int[][]{{0,1},{0,-1},{-1,0},{1,0}};
int sum = 0;
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[0].length; j++) {
if(grid[i][j] == 1) {
int lines = 4;
//判断这个岛旁边连接了多少个岛
for (int[] dir : directions) {
int newX = i + dir[0];
int newY = j + dir[1];
if (newX >= 0 && newX < grid.length && newY >= 0 && newY < grid[0].length && grid[newX][newY] == 1)
lines--;
}
sum += lines;
}
}
}
return sum;
}
}