LeetCode刷题之深度优先遍历

2,441 阅读9分钟

DFS:深度优先遍历

  1. 广度优先遍历是一层一层遍历,在遍历过程中遇到新的节点就存储起来,等当前层遍历完之后再遍历下一层。
  2. 深度优先遍历是遍历过程中遇到新节点就遍历新节点,直到没有新节点就返回到根节点继续遍历。需要用栈存储当前节点信息,对已经遍历过的节点需要做标记。
  • 常用来解决“可达性”问题

695. 岛屿的最大面积(Medium)

给定一个包含了一些 0 和 1的非空二维数组 grid , 一个岛屿是由四个方向 (水平或垂直) 的 1 (代表土地) 构成的组合。你可以假设二维矩阵的四个边缘都被水包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为0。)

示例 1:

[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
对于上面这个给定矩阵应返回 6。注意答案不应该是11,因为岛屿只能包含水平或垂直的四个方向的‘1’。

示例 2:

[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0。

注意: 给定的矩阵grid 的长度和宽度都不超过 50。

题解:寻找岛屿的最大面积,能够想到的就是dfs,遍历数组,遇到为1的面积加1,然后当前岛屿值置为0,继续往四个方向dfs,最后返回的就是最大岛屿面积。

class Solution {

    //定义四个方向
    private int[][] direction =  {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    private int m;
    private int n;

    public int maxAreaOfIsland(int[][] grid) {

        if (grid == null || grid.length == 0){
            return 0;
        }
        
        int maxArea = 0;
        m = grid.length;
        n = grid[0].length;
        //遍历岛屿
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                maxArea = Math.max(maxArea, dfs(grid, i, j));
            }
        }
        return maxArea;
    }

    //dfs
    private int dfs(int[][] grid, int r, int t){
        if (r < 0 || r >= m || t < 0 || t >= n || grid[r][t] == 0){
            return 0;
        }
        //遍历到的岛屿置为0
        grid[r][t] = 0;
        int area = 1;
        //往四个方向dfs
        for (int[] d : direction) {
            area += dfs(grid, r + d[0], t + d[1]);
        }
        return area;
    }
}

200. 岛屿数量(Medium)

给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

示例 1:

输入:
11110
11010
11000
00000
输出: 1

示例 2:

输入:
11000
11000
00100
00011
输出: 3

题解:与上道题类似,不同的是这道题求的是岛屿的数量,上道题求的是岛屿最大面积。也可以用dfs,遍历数组,遇到值为1的就岛屿数量加1,接着值置为0,然后以这个点为出发,往四个方向进行dfs,遇到连通的岛屿值置为0,直到不能dfs为止。当数组遍历完之后,岛屿数量也就出来了。

class Solution {
    //定义四个方向
    private int m, n;
    private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }
        m = grid.length;
        n = grid[0].length;
        int islandsNum = 0;
        //遍历二维数组
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                //遇到值为1的就岛屿数量加一,然后往四周dfs
                if (grid[i][j] != '0') {
                    dfs(grid, i, j);
                    islandsNum++;
                }
            }
        }
        return islandsNum;
    }

    //dfs
    private void dfs(char[][] grid, int i, int j) {
        if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') {
            return;
        }  
        //将当前值置为0
        grid[i][j] = '0';
        for (int[] d : direction) {
            dfs(grid, i + d[0], j + d[1]);
        }
    }
    
}

547. 朋友圈(Medium)

班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。

给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

示例 1:

输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。

示例 2:

输入:
[[1,1,0],
[1,1,1],
[0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。

注意:

N 在[1,200]的范围内。
对于所有学生,有M[i][i] = 1。
如果有M[i][j] = 1,则有M[j][i] = 1。

题解:

  • 首先分析题意:一共有n个学生,组成n*n的矩阵M,M[i][j]=1表示i和j是朋友,求出朋友圈的数量。观察矩阵,M[i][j]数字表示第i个或第j个人,在第一行M[0][j],如果值为1,表示第0个人与这些人是朋友,在列中也相同。
  • 所以可以准备一个数组标记是否访问过,遍历矩阵第一行,遇到遍历过的直接跳过,没有遍历过的用dfs。
class Solution {
    //人数
    private int n;

    public int findCircleNum(int[][] M) {
        n = M.length;
        int circleNum = 0;
        boolean[] visited = new boolean[n];
        for (int i = 0; i < n; i++) {
            //没有遍历过的就进行dfs
            if (!visited[i]){
                dfs(M, i, visited);
                circleNum++;
            }
        }
        return circleNum;
    }

    //dfs
    private void dfs(int[][] M, int i, boolean[] visited) {
        //遍历过的标记为true
        visited[i] = true;
        //在同一列进行dfs,同一列值为1的表示是朋友关系,然后在从值为1且没访问过的点出发进行dfs
        for (int k = 0; k < n; k++) {
            if (M[i][k] == 1 && !visited[k]){
                dfs(M, k, visited);
            }
        }
    }
}

130. 被围绕的区域(Medium)

给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。

找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。

示例:

X X X X
X O O X
X X O X
X O X X

运行你的函数后,矩阵变为:

X X X X
X X X X
X X X X
X O X X

解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

题解:

  • 一开始想到的方法是从矩阵内部进行dfs,遇到的每个O都变成X,dfs如果最后O在外围,那么就倒退回去,但是这个方法很麻烦。
  • 从他人的解答之后才知道可以先遍历外圈进行dfs,遍历到的O变成¥,这样剩下的就是被包围的O了,这时候将剩下的O变成X,然后将$变成O就恢复了。
  • 这种解法的思想是先将不合条件的排除掉,剩下的就是答案了,也就是排除法
class Solution {
    public void solve(char[][] board) {
        //遍历外圈
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                //如果外圈中发现'O',进行dfs,将遇到的O变成$
                if ((i == 0 || i == board.length - 1 || j == 0 || j == board[0].length - 1) && board[i][j] == 'O'){
                    solveDFS(board, i, j);
                }
            }
        }
        //将被包围的'O'-->'X','$'-->'O'
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (board[i][j] == 'O'){
                    board[i][j] = 'X';
                }
                if (board[i][j] == '$'){
                    board[i][j] = 'O';
                }
            }
        }
    }

    private void solveDFS(char[][] board, int i, int j) {
        //遇到的'O'-->'$'
        if (board[i][j] == 'O'){
            board[i][j] = '$';
            //往四个方向进行dfs
            //左
            if (i > 0 && board[i - 1][j] == 'O'){
                solveDFS(board, i - 1, j);
            }
            //右
            if (i < board.length - 1 && board[i + 1][j] == 'O'){
                solveDFS(board, i + 1, j);
            }
            //上
            if (j > 0 && board[i][j - 1] == 'O'){
                solveDFS(board, i, j - 1);
            }
            //下
            if (j < board[0].length - 1 && board[i][j + 1] == 'O'){
                solveDFS(board, i, j + 1);
            }
        }
    }
}

417. 太平洋大西洋水流问题(Medium)

给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。

规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。

请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。

提示:

输出坐标的顺序不重要 m 和 n 都小于150

示例:

给定下面的 5x5 矩阵:

太平洋  ~   ~   ~   ~   ~
  ~  1   2   2   3  (5)  *
  ~  3   2   3   (4)  (4)  *
  ~  2   4  (5)   3   1   *
  ~  (6)  (7)   1   4   5   *
  ~  (5)   1   1   2   4   *
    *   *   *   *   *  大西洋

返回:

[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (上图中带括号的单元).

题解:

  • 题目让我们找的是哪些点的水流能够流到太平洋和大西洋,一开始想到的办法是遍历所有的点进行dfs,判断哪些点是能够流到四条边的两 边(太平洋和大西洋都要有),但是这样的方法比较复杂,看了其他博主的解答之后才知道此题跟上一题很类似。
  • 遍历四条边的点进行dfs,找到哪些点是能够到达的,然后取太平洋和大西洋的交际就是答案了。
class Solution {
    private int m;
    private int n;
    private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    private int[][] matrix;

    public List<List<Integer>> pacificAtlantic(int[][] matrix) {

        List<List<Integer>> result = new ArrayList<>();
        if (matrix == null || matrix.length == 0){
            return result;
        }

        m = matrix.length;
        n = matrix[0].length;
        this.matrix = matrix;
        //用两个数组标记哪些点是能到太平洋和大西洋的
        boolean[][] canReachP = new boolean[m][n];
        boolean[][] canReachA = new boolean[m][n];

        //对左右两条边进行dfs
        for (int i = 0; i < m; i++) {
            dfs(i, 0, canReachP);
            dfs(i, n - 1, canReachA);
        }

        //对上下两条边进行dfs
        for (int i = 0; i < n; i++) {
            dfs(0, i, canReachP);
            dfs(m - 1, i, canReachA);
        }

        //找交集
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (canReachA[i][j] && canReachP[i][j]){
                    result.add(Arrays.asList(i, j));
                }
            }
        }
        return result;
    }

    //dfs
    private void dfs(int r, int c, boolean[][] canReach) {
        //访问过的话直接返回
        if (canReach[r][c]){
            return;
        }
        //标记为true
        canReach[r][c] = true;
        //对四个方向进行dfs
        for (int[] d : direction) {
            int nextR = r + d[0];
            int nextC = c + d[1];
            if (nextR < 0 || nextR >= m || nextC < 0 || nextC >= n || matrix[r][c] > matrix[nextR][nextC]){
                continue;
            }
            dfs(nextR, nextC, canReach);
        }
    }
}