岛屿类问题通用解法:DFS
题目一:岛屿的数量
思路:
①遍历每一个位置,若当前位置为陆地,使用一次DFS(检查上下左右四个点),并将此次DFS中发现是陆地的位置改值,防止陷入死循环,那么主函数中使用DFS的总次数(不包括迭代的)就是岛屿的数量
②因为要检查每个位置的上下左右,因此要做范围检测,即所谓“先污染后治理”,超出范围的直接返回
代码:
class Solution {
public int numIslands(char[][] grid) {
int rows = grid.length;//行数
int columns = grid[0].length;//列数
int islandnums = 0;
for(int i = 0; i < rows; i++){
for(int j = 0; j < columns; j++){
if(grid[i][j] == '1'){
islandnums++;
dfs(i, j, grid);
}
}
}
return islandnums;
}
public void dfs(int i, int j, char[][] grid){
if(!check(grid.length, grid[0].length, i, j) || grid[i][j] != '1'){//检查是否在范围内且是否是陆地
return;
}
grid[i][j] = '2';//是陆地的话更改值,防止多次重复遍历造成死循环
dfs(i - 1, j, grid);
dfs(i + 1, j, grid);
dfs(i, j - 1, grid);
dfs(i, j + 1, grid);
}
//检查网格是否在范围内
public boolean check(int rows, int columns,int i, int j){
return 0 <= i && i < rows && 0 <= j && j < columns;
}
}
复杂度分析:
①时间复杂度:O(MN)
②空间复杂度:O(MN)//因为递归
题目二:岛屿的周长
思路一:
按顺序遍历每块陆地,判断四个边是否和陆地相连即可
class Solution {
public int islandPerimeter(int[][] grid) {
int rows = grid.length;//行数
int columns = grid[0].length;//列数
int nums = 0;
for(int i = 0; i < rows; i++){
for(int j = 0; j < columns; j++){
if(grid[i][j] == 1){//判断四条边是否和陆地相连
if(!check(rows, columns, i - 1, j) || grid[i-1][j] == 0)
nums++;
if(!check(rows, columns, i + 1, j) || grid[i+1][j] == 0)
nums++;
if(!check(rows, columns, i, j - 1) || grid[i][j - 1] == 0)
nums++;
if(!check(rows, columns, i, j + 1) || grid[i][j + 1] == 0)
nums++;
}
}
}
return nums;
}
//检查网格是否在范围内
public boolean check(int rows, int columns,int i, int j){
return 0 <= i && i < rows && 0 <= j && j < columns;
}
}
复杂度分析:
①时间复杂度:O(MN)
②空间复杂度:O(1)
思路2:DFS(其实没必要)
超界,周长+1;临边是海洋,周长+1;临边是陆地,周长+0
代码:
class Solution {
public int islandPerimeter(int[][] grid) {
int rows = grid.length;//行数
int columns = grid[0].length;//列数
int nums = 0;
for(int i = 0; i < rows; i++){
for(int j = 0; j < columns; j++){
if(grid[i][j] == 1){
nums += dfs(i, j, grid);
return nums;//因为只有一块岛屿,找到一片陆地后就会查找整块岛屿
}
}
}
return 0;
}
public int dfs(int i, int j, int[][] grid){
if(!check(grid.length, grid[0].length, i, j)){//检查是否超出边界,超长则周长+1
return 1;
}
if(grid[i][j] == 0)//检查是否是海洋
return 1;
if(grid[i][j] == 2)//检查是否遍历过这块陆地
return 0;
grid[i][j] = 2;//是陆地的话更改值,防止多次重复遍历造成死循环
return//返回结果
dfs(i - 1, j, grid) +
dfs(i + 1, j, grid) +
dfs(i, j - 1, grid) +
dfs(i, j + 1, grid);
}
//检查网格是否在范围内
public boolean check(int rows, int columns,int i, int j){
return 0 <= i && i < rows && 0 <= j && j < columns;
}
}
复杂度分析:
①时间复杂度:O(MN)
②空间复杂度:O(MN)
题目三:岛屿的最大面积
思路:
①和求岛屿周长的思路差不多,求周长是岛屿遇到边界和海洋时+1,求面积时是遇到陆地caijia
②当是没遍历过的陆地时,DFS在返回时要加上1(当前块的面积)
代码:
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int rows = grid.length;//行数
int columns = grid[0].length;//列数
int max_area = 0;
for(int i = 0; i < rows; i++){
for(int j = 0; j < columns; j++){
if(grid[i][j] == 1){
int temp_area = dfs(i, j, grid);
max_area = max_area < temp_area ? temp_area : max_area;
}
}
}
return max_area;
}
public int dfs(int i, int j, int[][] grid){
if(!check(grid.length, grid[0].length, i, j) || grid[i][j] != 1){//检查是否在范围内且是否是海洋或遍历过的陆地
return 0;
}
grid[i][j] = 2;//是没遍历过陆地的话更改值,防止多次重复遍历造成死循环
return
1 + //当前块的面积
dfs(i - 1, j, grid) +
dfs(i + 1, j, grid) +
dfs(i, j - 1, grid) +
dfs(i, j + 1, grid);
}
//检查网格是否在范围内
public boolean check(int rows, int columns,int i, int j){
return 0 <= i && i < rows && 0 <= j && j < columns;
}
}
复杂度分析:
①时间复杂度:O(MN)
②空间复杂度:O(MN)//因为递归
题目四:被围绕的区域
题目:
思路:
①这题乍一看和判断岛屿数量思路差不多,但是这里岛屿的定义变为完全被海洋包围,被边界和海洋包围的不再作为岛屿;
因此思路应从遍历每个位置找岛屿变为遍历边界上的每个位置找岛屿,可以发现,不被"X"包围的"O"一定直接或间接的与边界上的"O"有连接
②DFS连接按模板来。要注意的是这里不会再更改未被海洋包含的陆地的值,但未被海洋包围的陆地的值会被更改,在主函数中应做相应处理
代码:
class Solution {
public void solve(char[][] grid) {
int rows = grid.length;//行数
if(rows == 0)
return;
int columns = grid[0].length;//列数
int islandnums = 0;
for(int j = 0; j < columns; j++){//遍历首行和末行
dfs(0, j, grid);
dfs(rows - 1, j, grid);
}
for(int i = 0; i < rows; i++){//遍历首列和末列
dfs(i, 0, grid);
dfs(i, columns - 1, grid);
}
for(int i = 0; i < rows; i++){
for(int j = 0; j < columns; j++){
if(grid[i][j] == 'O')//是被海洋包围的陆地
grid[i][j] = 'X';
if(grid[i][j] == 'A')//未被海洋包围的陆地,更改回陆地
grid[i][j] = 'O';
}
}
}
public void dfs(int i, int j, char[][] grid){
if(!check(grid.length, grid[0].length, i, j)){//检查是否在范围内
return;
}
if(grid[i][j] == 'X')//检查是否是海洋
return;
if(grid[i][j] == 'A')//检查是否是遍历过的陆地
return;
grid[i][j] = 'A';//是陆地的话更改值,防止多次重复遍历造成死循环
dfs(i - 1, j, grid);
dfs(i + 1, j, grid);
dfs(i, j - 1, grid);
dfs(i, j + 1, grid);
}
//检查网格是否在范围内
public boolean check(int rows, int columns,int i, int j){
return 0 <= i && i < rows && 0 <= j && j < columns;
}
}
复杂度分析:
①时间复杂度:O(MN)
②空间复杂度:O(MN)//因为递归
题目五:最大人工岛
https://leetcode-cn.com/problems/making-a-large-island/
总结:
网格的DFS实际上是树的DFS的延伸
网格的DFS遍历上下左右四格,树的DFS遍历左右子节点
二叉树的DFS
void traverse(TreeNode root) {
// 判断 base case
if (root == null) {
return;
}
// 访问两个相邻结点:左子结点、右子结点
traverse(root.left);
traverse(root.right);
}
网格的DFS
void dfs(int[][] grid, int r, int c) {
// 判断 base case
// 检查是否超出网格范围
if(!check(grid.length, grid[0].length, i, j))
return;
}
//检查是否是海洋
//检查是否为遍历过的陆地
//对为遍历过的陆地更改值,防止重复遍历陷入死循环
// 访问上、下、左、右四个相邻结点
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
// 判断坐标 (r, c) 是否在网格中
//检查网格是否在范围内
public boolean check(int rows, int columns,int i, int j){
return 0 <= i && i < rows && 0 <= j && j < columns;
}
参考:
https://leetcode-cn.com/problems/number-of-islands/solution/dao-yu-lei-wen-ti-de-tong-yong-jie-fa-dfs-bian-li-/