LeetCode刷题之DFS算法

1.基本思路及代码框架

使用DFS或BFS算法遍历二维数组

在二维矩阵中使用DFS搜索,就是把二维矩阵中的每一个位置看成一个节点,这个节点的上下左右四个位置就是相邻节点,那么整个可以抽象为一幅网状的图结构。

根据数据结构和算法思维的框架,根据二叉树的遍历框架写出二维矩阵的DFS代码框架:

//二叉树的遍历框架
void traverse(TreeNode *root) {
    //遍历本节点,前序遍历
    traverse(roo->left);
    //遍历本节点,中序遍历
    traverse(roo->right);
    //遍历本节点,后序遍历
}

//二维矩阵遍历框架
void dfs(vector<vector<int>> &grid, int i, int j, vector<bool> &visited) {
    int m = grid.size(), n = grid[0].size();
    
    //判断节点是否越界
    if (i < 0 || j < 0 || i >= m || j >= n) {
        return;
    }
    //判断是否访问过
    if (visited[i][j]) {
        return;
    }
    //访问节点(i,j)
    visited[i][j] = true;
    dfs(grid, i - 1, j);//上
    dfs(grid, i + 1, j);//下
    dfs(grid, i, j - 1);//左
    dfs(grid, i, j + 1);//右
}

因为二维数组本质是一个图,所以使用visited布尔数组可以避免走回头路。

这⾥额外说⼀个处理⼆维数组的常⽤⼩技巧,你有时会看到使⽤「⽅向数组」来处理上下左右的遍历,和前
⽂ 图遍历框架 的代码很类似:

// ⽅向数组,分别代表上、下、左、右
int[][] dirs = new int[][]{{-1,0}, {1,0}, {0,-1}, {0,1}};
void dfs(int[][] grid, int i, int j, boolean[] visited) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n) {
        // 超出索引边界
        return;
    }
    if (visited[i][j]) {
        // 已遍历过 (i, j)
        return;
    }
    // 进⼊节点 (i, j)
    visited[i][j] = true;
    // 递归遍历上下左右的节点
    for (int[] d : dirs) {
        int next_i = i + d[0];
        int next_j = j + d[1];
        dfs(grid, next_i, next_j);
    }
    // 离开节点 (i, j)
}

2.题目解答

1.LeetCode之200岛屿数量问题

  • 解题思路
    使用深度优先搜索,将每一个岛屿都淹没,避免维护visited数组。
  • 代码实现
/*
 * @lc app=leetcode.cn id=200 lang=cpp
 *
 * [200] 岛屿数量
 */

// @lc code=start
class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        int res = 0;
        int m = grid.size(), n = grid[0].size();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '1') {
                    ++res;
                    dfs(grid, i, j);//将与此块连接的块全部淹没
                }
            }
        }
        return res;
    }

    void dfs(vector<vector<char>> &grid, int i, int j) {
        int m = grid.size(), n = grid[0].size();
        
        if (i < 0 || j < 0 || i >= m || j >= n) {
            return;
        }

        if (grid[i][j] == '0') {
            return;
        }

        grid[i][j] = '0';
        dfs(grid, i - 1, j);
        dfs(grid, i + 1, j);
        dfs(grid, i, j - 1);
        dfs(grid, i, j + 1);
    }
};
// @lc code=end

2.LeetCode之1254统计封闭岛屿的数量

  • 解题思路
  • 与上面思路一致,不过本题不同的是周围靠边的不算岛屿,所以先要将四周的岛屿进行淹没,然后再进行深度遍历。
  • 除了使用DFS/BFS算法以外,还可以使用Union Find算法运用(后续学习在实现)。
  • 代码实现
/*
 * @lc app=leetcode.cn id=1254 lang=cpp
 *
 * [1254] 统计封闭岛屿的数目
 */

// @lc code=start
class Solution {
public:
    int closedIsland(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        for (int i = 0; i < n; i++) {
            dfs(grid, 0, i);
            dfs(grid, m - 1, i);
        }

        for (int j = 0; j < m; j++) {
            dfs(grid, j, 0);
            dfs(grid, j, n - 1);
        }
        int res = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++){
                if (grid[i][j] == 0) {
                    ++res;
                    dfs(grid, i, j);
                }
            }
        }
        return res;
    }

    void dfs(vector<vector<int>> &grid, int i, int j) {
        int m = grid.size(), n = grid[0].size();
        if (i < 0 || j < 0 || i >= m || j >= n) {
            return;
        }

        if (grid[i][j] == 1) {
            return;
        }

        grid[i][j] = 1;
        dfs(grid, i - 1, j);
        dfs(grid, i + 1, j);
        dfs(grid, i, j - 1);
        dfs(grid, i, j + 1);
    }
};
// @lc code=end

3.LeetCode之1020飞地的数量

  • 解题思路
    与上面思路一致,将周围的岛屿淹没后,统计剩下陆地块的数量即为所求。
  • 代码实现
/*
 * @lc app=leetcode.cn id=1020 lang=cpp
 *
 * [1020] 飞地的数量
 */

// @lc code=start
class Solution {
public:
    int numEnclaves(vector<vector<int>>& grid) {
         int m = grid.size(), n = grid[0].size();
        for (int i = 0; i < n; i++) {
            dfs(grid, 0, i);
            dfs(grid, m - 1, i);
        }

        for (int j = 0; j < m; j++) {
            dfs(grid, j, 0);
            dfs(grid, j, n - 1);
        }
        
        int res = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    res += 1;
                }
            }
        }
        return res;
    }

    void dfs(vector<vector<int>> &grid, int i, int j) {
        int m = grid.size(), n = grid[0].size();
        
        if (i < 0 || j < 0 || i >= m || j >=n) {
            return;
        }

        if (grid[i][j] == 0) {
            return;
        }

        grid[i][j] = 0;
        dfs(grid, i - 1, j);
        dfs(grid, i + 1, j);
        dfs(grid, i, j - 1);
        dfs(grid, i, j + 1);
    }
};
// @lc code=end

4.LeetCode之695岛屿的最大面积

  • 解题思路
    遍历岛屿,计算每个岛屿的面积,返回最大的面积。
  • 代码实现
/*
 * @lc app=leetcode.cn id=695 lang=cpp
 *
 * [695] 岛屿的最大面积
 */

// @lc code=start
class Solution {
public:
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();

        int maxSize = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0 ; j < n; j++) {
                int s = 0;
                if (grid[i][j] == 1) {
                    dfs(grid, i, j, s);
                    maxSize = max(maxSize, s);
                }
            }
        }
        return maxSize;
    }

    void dfs(vector<vector<int>> &grid, int i, int j, int &s) {
        int m = grid.size(), n = grid[0].size();
        
        if (i < 0 || j < 0 || i >= m || j >=n) {
            return;
        }

        if (grid[i][j] == 0) {
            return;
        }

        grid[i][j] = 0;
        s = s + 1;
        dfs(grid, i - 1, j, s);
        dfs(grid, i + 1, j, s);
        dfs(grid, i, j - 1, s);
        dfs(grid, i, j + 1, s);
    }
};
// @lc code=end

5.LeetCode之1905统计子岛屿

  • 解题思路
    如果岛屿2中有的陆地而岛屿1中对应的位置为水,说明这个陆地所处的岛屿肯定不是子岛屿,可直接将其淹没。剩下的岛屿都为子岛屿。
  • 代码实现
/*
 * @lc app=leetcode.cn id=1905 lang=cpp
 *
 * [1905] 统计子岛屿
 */

// @lc code=start
class Solution {
public:
    int countSubIslands(vector<vector<int>>& grid1, vector<vector<int>>& grid2) {
         int m = grid2.size(), n = grid2[0].size();
         for (int i = 0; i < m; i++) {
             for (int j = 0; j < n; j++) {
                 if (grid1[i][j] == 0 && grid2[i][j] == 1) {
                     dfs(grid2, i ,j);
                 }
             }
         }
         int res = 0;
         for (int i = 0; i < m; i++) {
             for (int j = 0; j < n; j++) {
                 if (grid2[i][j] == 1) {
                     ++res;
                     dfs(grid2, i, j);
                 }
             }
         }
         return res;
    }


   void dfs(vector<vector<int>> &grid, int i, int j) {
        int m = grid.size(), n = grid[0].size();
        if (i < 0 || j < 0 || i >= m || j >= n) {
            return;
        }

        if (grid[i][j] == 0) {
            return;
        }

        grid[i][j] = 0;
        dfs(grid, i - 1, j);
        dfs(grid, i + 1, j);
        dfs(grid, i, j - 1);
        dfs(grid, i, j + 1);
    }
};
// @lc code=end

6.LeetCode之694不同的岛屿数量

  • 看完图的遍历和二叉树的序列化和反序列化再来写。