【DFS和BFS】力扣题解

一、DFS和BFS相关概念

深度优先搜索(DFS)在进行图的搜索时,得到一个新的节点,立即对新节点向下遍历,然后回溯。使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 可达性 问题。

广度优先搜索(BFS)在搜索的时候,是一层一层地进行遍历,每层遍历都是以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。

每一层遍历的节点都与根节点距离相同。设 di 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di <= dj。

利用这个结论,可以求解最短路径等 最优解 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径,无权图是指从一个节点到另一个节点的代价都记为 1。

二、DFS相关练习题

1.岛屿的最大面积

给定一个包含了一些 0 和 1 的非空二维数组 grid 。

一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 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。

解题思路:

当登陆某一个岛屿后,以该岛屿的东、西、南、北四个方向探索,同时为了保证不重复访问,将访问过得岛屿标记为2,如果遇到‘0’或者2就退出。

class Solution {

    private int[][] derection = {{-1,0},{1,0},{0,-1},{0,1}};
    private int m, n;
    private int[][] grid;  
      
    public int maxAreaOfIsland(int[][] grid) {
        if(grid == null || grid.length == 0){
            return 0;
        }
        m = grid.length;
        n = grid[0].length;
        int res = 0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n;j++){
                if(grid[i][j] == 1){
                    res = Math.max(res,dfs(grid,i,j));
                }
            }
        }
        return res;
    }

    public int dfs(int[][] grid,int r,int c) {
   		//判断边界,以及是否是岛屿或是否被访问过
        if(r < 0 || r >= m || c < 0 || c >= n || grid[r][c] != 1){
            return 0;
        }
        //访问过,设置为2
        grid[r][c] = 2;
        int res = 1;
       	//以当前节点,想四个方向搜索
        for(int d[] : derection){
            res += dfs(grid,r+d[0],c+d[1]);
        }
        return res;
    }


}

2.岛屿的数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:

11110
11010
11000
00000

输出: 1

示例 2:

输入:

11000
11000
00100
00011

输出: 3

解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。

class Solution {

    private int[][] derection = {{-1,0},{1,0},{0,1},{0,-1}};
    private int m, n;
    private char[][] grid;
    
    public int numIslands(char[][] grid) {
        if(grid == null || grid.length == 0){
            return 0;
        }
        m = grid.length;
        n = grid[0].length;
        int res = 0;
        for(int i = 0;i < m; i++){
            for(int j = 0;j < n; j++){
            	//只有是岛屿才进行搜索
                if(grid[i][j] == '1'){
                    dfs(grid,i,j);
                    res++;
                }
            }
        }
        return res;
    }
    private void dfs(char[][] grid,int r, int c){

        if(r < 0 || r >= m || c < 0 || c >= n || grid[r][c] != '1'){
            return;
        }
        grid[r][c] = '2';
        for(int[] d : derection){
            dfs(grid,r+d[0],c+d[1]);
        }
        return;
    }
}

3.朋友圈

班上有 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。

 class Solution {
    private int[][] M;
    private int m;

    public int findCircleNum(int[][] M) {
        if(M == null || M.length == 0){
            return 0;
        }
        m = M.length;
        //设置一个辅助数组用来存储节点是否被访问过
        boolean[] isVisited = new boolean[m];
        int res =0;
        //对矩阵一行行的判断
        for(int i = 0; i < m; i++){
        	//如果没有被访问过,从这个定点,一直向下搜索
            if(!isVisited[i]){
                dfs(M,i,isVisited);
                res++;
            }
        }
        return res;
    }
    private void dfs(int[][]M,int i,boolean[] isVisited){
    	//首先设置为已访问
        isVisited[i] = true;
        //循环遍历每一列
        for(int j = 0;j < m; j++){
        	//当两节点有联系,且未被访问,对该节点继续向下搜索。
            if(M[i][j] == 1 && !isVisited[j]){
                dfs(M,j,isVisited);
            }
        }
    }
}

4.被围绕的区域

给定一个二维的矩阵,包含 ‘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’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

class Solution {

    private int[][] derection = {{-1,0},{1,0},{0,1},{0,-1}};
    private int m,n;
    private char[][] board; 

    public void solve(char[][] board) {
        if(board == null || board.length == 0){
            return;
        }
        m = board.length;
        n = board[0].length;
		//从第一行和最后一行开始搜索
        for(int i = 0;i < n; i++){
            dfs(board,0,i);
            dfs(board,m-1,i);
        }
        //从第一列和最后一列开始搜索
        for(int i = 0;i < m; i++){
            dfs(board,i,0);
            dfs(board,i,n-1);
        }

        for(int i = 0;i < m; i++){
            for(int j = 0;j < n; j++){
            	//将所有的‘O’设置为‘X’
                if(board[i][j] == 'O'){
                    board[i][j] = 'X';
                }
                //将所有的'#'改为‘O’
                if(board[i][j] == '#'){
                    board[i][j] = 'O';
                }
            }
        }
        return;
    }

    private void dfs(char[][] board,int r,int c){
    
        if(r < 0 || r >= m || c < 0 || c >= n || board[r][c] == 'X' || board[r][c] == '#'){
            return;
        }
        //将与边界相连的'O'设置为‘#’
        board[r][c] = '#';
        //搜索与边界相连接的‘O‘’
        for(int[] d : derection){
            dfs(board,r+d[0],c+d[1]);
        }
    }
       
}

5.太平洋大西洋水流问题

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

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

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

提示:
加粗样式
输出坐标的顺序不重要
m 和 n 都小于150

示例:

给定下面的 5x5 矩阵:
在这里插入图片描述
返回:

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

class Solution {

    private int[][] deriect = {{1,0},{-1,0},{0,1},{0,-1}};
    private int m,n;

    public List<List<Integer>> pacificAtlantic(int[][] matrix) {
        List<List<Integer>> list = new ArrayList<>();
        if(matrix == null || matrix.length == 0){
            return list;
        }
        m = matrix.length;
        n = matrix[0].length;
        boolean[][] checkedP = new boolean[m][n];
        boolean[][] checkedA = new boolean[m][n];
        for(int i = 0;i < m;i++){
            dfs(matrix,i,0,checkedP);
            dfs(matrix,i,n-1,checkedA);
        }
        for(int i = 0;i < n;i++){
            dfs(matrix,0,i,checkedP);
            dfs(matrix,m-1,i,checkedA);
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (checkedP[i][j] && checkedA[i][j]) {
                    list.add(Arrays.asList(i, j));
                }
            }
        } 
        return list;
        
    
    }

    private void dfs(int[][] matrix,int r, int c, boolean[][] checked){
        if(checked[r][c]){
            return;
        }
        checked[r][c] = true;
        for(int[] d : deriect){
            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(matrix,nextR,nextC,checked);
        }
        return ;
    }
}

三、BFS相关练习题

1. 二进制矩阵中的最短路径

在一个 N × N 的方形网格中,每个单元格有两种状态:空(0)或者阻塞(1)。
一条从左上角到右下角、长度为 k 的畅通路径,由满足下述条件的单元格 C_1, C_2, ..., C_k组成:

class Solution {
    //八个方向联通
    private int[][] derection = {{1,-1},{1,0},{1,1},{-1,-1},{-1,0},{-1,1},{0,1},{0,-1}};    
    private int m,n;
    public int shortestPathBinaryMatrix(int[][] grid) {
        if(grid == null || grid.length == 0){
            return -1;
        }
        m = grid.length;
        n = grid[0].length;
        //如果起始点或者终点是阻塞状态1,代表永远不可达,不存在路径
        if(grid[0][0] == 1 || grid[m-1][n-1] == 1){
            return -1;
        }
        //直接使用grid存储路径长度
        grid[0][0] = 1;

        Queue<int[]> queue = new LinkedList<>();
        //将起始点存入到队列中
        queue.add(new int[]{0,0});
        
        //队列不为空 且终点是可达的,也就是说 目前为止没有任何一条路经可以到达终点
        while(!queue.isEmpty() && grid[m-1][n-1] == 0){
            int[] xy = queue.remove();
            int length = grid[xy[0]][xy[1]];
            for(int[] d : derection){
                int nextR = xy[0] + d[0];
                int nextC = xy[1] + d[1];
                if(inGrid(nextR,nextC) && grid[nextR][nextC] == 0){
                    queue.add(new int[]{nextR,nextC});
                    grid[nextR][nextC] = length + 1;
                }
            }
        }
        return grid[m-1][n-1] == 0 ? -1 : grid[m-1][n-1];
    }

    //判断边界,是否在矩阵范围之内
    private boolean inGrid(int r,int c){
        return r>=0 && r < m && c >=0 && c <  n;
    }
}

2.单词接龙

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。

说明:

  • 如果不存在这样的转换序列,返回 0。
  • 所有单词具有相同的长度。
  • 所有单词只由小写字母组成。
  • 字典中不存在重复的单词。
  • 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

示例 1:

输入:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]

输出: 5

解释: 一个最短转换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
返回它的长度 5。

示例 2:

输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]

输出: 0

解释: endWord “cog” 不在字典中,所以无法进行转换。

class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        
        //首先将wordList存入到HashSet中
        Set<String> wordSet = new HashSet<>(wordList);
        if(wordSet.size() == 0 || !wordSet.contains(endWord)){
            return 0;
        }    
        wordSet.remove(beginWord);

        Queue<String> queue = new LinkedList<>();
        queue.add(beginWord);

        Set<String> visited = new HashSet<>();
        visited.add(beginWord);

        int wordLen = beginWord.length();

        int step = 1;

        while(!queue.isEmpty()){
            //计算某一层次,有多少节点
            int size = queue.size();
            //依次遍历当前节点中的单词
            for(int i = 0;i < size;i++){
                String word = queue.remove();
                char[] charArray = word.toCharArray();
                //修改每一个字符
                for(int j = 0;j < wordLen;j++){
                    //保存原始的字符
                    char originChar = charArray[j];
                    for(char k = 'a';k <= 'z'; k++){
                        //如果修改之后跟原来的字符串相同,就跳过
                         if (k == originChar) {
                            continue;
                        }
                        charArray[j] = k;
                        //将字符数组转换成字符串
                        String nextWord = String.valueOf(charArray);
                        //判断字典中是否存在修改后的字符
                        if(wordSet.contains(nextWord)){
                            //如果修改后的单词与结束单词相同
                            if(nextWord.equals(endWord)){
                                return step+1;
                            }
                            //如果不同,且未被访问过
                            if(!visited.contains(nextWord)){
                                queue.add(nextWord);
                                visited.add(nextWord);
                            }
                        }
                    }
                    //把单词恢复成原始的单词
                    charArray[j] = originChar;
                }
            }
            //这一层次全部遍历完毕,step+1
            step++;
        }
        return 0;
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值