【算法】广度优先算法BFS

一、算法理解

广度优先搜索算法(Breadth-First-Search,BFS),又称宽度优先搜索。作为最简便的图的搜索算法之一,是很多重要图算法的基本原型,如Dijkstra最短路径算法和Prime最小生成树算法。其核心思想是:

  • 从初始节点开始,应 用产生式规则生成第一层节点,检查目标节点是否在这些后继节点中。
  • 若没有,则再用产生式规则将第一层所有节点逐一扩展,得到第二层节点,并逐一检查第二层节点中是否包含目标节点。
  • 若没有,则继续扩展第二层的节点。
  • 如此类推,直至发现目标节点为止。

广度优先搜索是一种分层的查找过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有往回退的情况,因此它不是一个递归的算法。为了实现逐层的访问,算法必须借助一个辅助队列,以记录正在访问的顶点的下一层 顶点。

【样例便于理解】:
image

  1. 首先从顶点A开始,将 A 入队列Queue , visited[A]=true ,表示顶点A已被访问;
  2. 此时队列 Queue 非空,取出队头元素A,将其相邻的未被访问的顶点(B,C)依次访问,并插入队列 Queue ;
  3. 队列非空,取出队头元素B,将其将其相邻的未被访问的顶点(D,E)依次访问,并插入队列 Queue ;
  4. 队列非空,取出队头元素C,将其将其相邻的未被访问的顶点(F,G)依次访问,并插入队列 Queue ;
  5. 取出队头元素D,无相邻未被访问顶点;
  6. 取出队头元素E,将其将其相邻的未被访问的顶点(H)依次访问,并插入队列 Queue ;
  7. 取出队头元素F,无相邻未被访问顶点;
  8. 取出队头元素G,将其将其相邻的未被访问的顶点(I)依次访问,并插入队列 Queue ;
  9. 取出队头元素H,无相邻未被访问顶点;
  10. 取出队头元素I,无相邻未被访问顶点;
  11. 队列为空,循环跳出,遍历结果为: A->B->C->D->E->F->G->H->I ;
    image

伪码参考:

void BFS() {
     1. 初始化:遍历需要的队列Q、标志节点是否访问过visited
     2. 起始节点入队列:Q.push(startNode)
     3. 标志起始节点已经在队列当中了: visited[startNode] = true
     while (!Q.empty()) {//队列非空
        node = Q.poll() //需要遍历的节点出队列

        nearList = getNearList(node) //获取node节点的邻接节点集合
        for (nearNode : nearList) { //对每一个邻接点,执行
           if (visited[nearNode] != true) {//如果邻接点没有被访问
               Q.add(nearNode) //把邻接点加入队列,等待访问
               visited[nearNode] = true //标志邻接点已经在队列当中了:

二、适用场景

广度优先搜索算法可以用来解决两类问题:

  • 从节点A出发,有前往节点B的路径吗?(迷宫问题)
  • 从节点A出发,前往节点B的哪条路径 最短?(最短路径问题)

三、注意事项

【核心数据结构】:

  1. 一个数据结构记录无向图(主要图中连线关系)。
  2. 一个数据结构记录节点 是否被访问过 避免重复访问)。
  3. 一个 先进先出队列,记录遍历过程。如果需要返回深度,队列每个元素同时 节点 在无向图中层级。

四、使用案例

1)单词接龙☆☆☆

字典 wordList 中从单词 beginWord 和 endWord 的转换序列是一个按下述规格形成的序列:

  1. 序列中第一个单词是 beginWord 。
  2. 序列中最后一个单词是 endWord 。
  3. 每次转换只能改变一个字母。
  4. 转换过程中的中间单词必须是字典 wordList 中的单词。
    给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord的最短转换序列中的单词数目 。如果不存在这样的转换序列,返回 0。

示例 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" 不在字典中,所以无法进行转换。

提示:
1 <= beginWord.length <= 10
endWord.length == beginWord.length
1 <= wordList.length <= 5000
wordList[i].length == beginWord.length
beginWord、endWord 和 wordList[i] 由小写英文字母组成
beginWord != endWord
wordList 中的所有字符串 互不相同

【问题分析】
根据题目意思,我们可以抽象地认为这是一个图的最短路径问题,初始节点为 beginWord ,目标节点为 endWord ,
中间节点为 wordList 给定的单词,现在我们已经明确了开始节点和目标节点,但是中间节点的邻接关系我们还没有确定,根据题目中每次转换只能改变一个字母这一条件,明确只有一个字母差距的单词可以连上一条边,构成邻接关系。因此,可以将示例1的输入构成构成一个无向图,如下(图片来源LeetCode):
image
至此,题目变成了一个寻找目标节点的最短路径问题,因此可以使用 “广度优先算法” 求解。

这道题的重点在于如何建立邻接关系,根据每次转换只能改变一个字母,可以先对wordList中的单词进行预处
理,将单词中的某个字母用"_"替换,这个预处理可以帮助我们构造一个单词变换的通用状态,例如: Hit->H_t<-
Hot 。

问题实现:

  1. 对给定的 wordList 做预处理,找出所有的通用状态。将通用状态记录在字典中,键是通用状态,值是所有
    具有通用状态的单词;
  2. 将包含 (beginWord, 1) 的元组放入队列中,beginWord 为初始节点,1表示初始节点的层数。我们需要返
    endWord 的层次也就是从 beginWord 出发的最短距离

数据结构:

  1. 定义HashMap<String, ArrayList>,记录节点之间无向树关系。
    注意:对于hot,来说,满足_ot、h_t、ho_都是其相邻节点。为了后续检索效率,分别作为不同的key,保存如HashMap。
  2. 定义ArrayQueue<Pair<String, Integer>>记录节点遍历队列。String记录节点值,Integer记录节点所在层级。
  3. 定义HashSet记录节点是否已遍历过,String记录节点值,

样例代码:

    public static int ladderLength(String beginWord, String endWord, List<String> wordList) {
        //wordList转换成记录的无向树
        HashMap<String, ArrayList<String>> tree = new HashMap<String, ArrayList<String>>();
        for(int i = 0; i < wordList.size(); i++) {
            String word = wordList.get(i);
            int length = word.length();
            for (int j = 0; j < length; j++) {
                String tmpStr = word.substring(0,j) + "_" + word.substring(j + 1, length);
                if(!tree.containsKey(tmpStr)) {
                    tree.put(tmpStr, new ArrayList<String>(Collections.singletonList(word)));
                }
                else {
                    tree.get(tmpStr).add(word);
                }
            }
        }

        //创建遍历队列:
        ArrayDeque<Pair<String,Integer>> queue = new ArrayDeque<Pair<String,Integer>>();
        HashSet<String> tags = new HashSet<String>();

        queue.addLast(new Pair<>(beginWord, 1));
        tags.add(beginWord);

        while(!queue.isEmpty()) {
            Pair<String, Integer> tmpPair = queue.pollFirst();
            String tmpWord = tmpPair.getKey();
            if (tmpWord.equals(endWord)) {
                return tmpPair.getValue();
            }

            for (int k = 0; k < tmpWord.length(); k++) {
                String tmpStr2 = tmpWord.substring(0, k) + "_" + tmpWord.substring(k + 1, tmpWord.length());
                ArrayList<String> tmpList = tree.get(tmpStr2);
                if (tmpList != null) {
                    for (String str : tmpList) {
                        if (str.equals(endWord)) {
                            return tmpPair.getValue() + 1;
                        } else if (!tags.contains(str)) {
                            queue.addLast(new Pair(str, tmpPair.getValue() + 1));
                            tags.add(str);
                        }
                    }
                }
            }
        }
        return 0;
    }

2)被围绕的区域

https://leetcode-cn.com/problems/surrounded-regions
给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
image

示例 1:
输入:board = [
​ ["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'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

示例 2:
输入:board = [["X"]]
输出:[["X"]]

提示:
m == board.length
n == board[i].length
1 <= m, n <= 200
board[i]/[j] 为 'X' 或 'O'

【思路分析】--暴力解发:

  1. 遍历二维数组中元素,如果是0,则以其为启动,向上下左右找1,直到最终所有方向都找到1,则刷新为。(并不行,每个0都需要上下左右)
  2. 基础处理下一元素。

【思路分析】:
把0节点按照上下左右组成树关系,如果最终所有叶子节点都是1,则说明是合围的。(边缘节点章节算叶子节点)
image

  1. DFS优先检索的,就是上下左右。
  2. 为了加快消息,增加一个序列。记录DFS过程中遇到的0位置,如果树中所有0是合围的,所有0节点全部置为1节点。

代码参考:

    public static void solve(char[][] board) {
        if(board.length == 0 || board[0].length == 0) {
            return;
        }

        for(int i = 0; i<board.length; i++) {
            for(int j=0; j <board[0].length; j++) {
                if(board[i][j] == 'O') {
                    judgeNodeIsTrue(board, i, j);
                }
            }
        }
    }

    public static void judgeNodeIsTrue(char[][] board, int rowIndex, int colIndex) {
        //定义队列
        ArrayDeque<Pair<Integer, Integer>> queue = new ArrayDeque<Pair<Integer, Integer>>();

        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        HashSet<Pair<Integer, Integer>> flags = new HashSet<Pair<Integer, Integer>>();
        //记录过程中遇到的0节点
        ArrayDeque<Pair<Integer, Integer>> zeroNodes = new ArrayDeque<Pair<Integer, Integer>>();

        int[][] direct = new int[][]{{0, -1}, {0 ,1}, {-1, 0}, {1, 0}};

        queue.addLast(new Pair<>(rowIndex, colIndex));
        flags.add(new Pair<>(rowIndex, colIndex));
        zeroNodes.addLast(new Pair<>(rowIndex, colIndex));

        while(!queue.isEmpty()) {
            Pair<Integer, Integer> node = queue.pollFirst();

            int row = node.getKey();
            int col = node.getValue();
            if(row == 0 || row == board.length - 1 || col == 0 || col == board[0].length - 1) {
                return;
            }

            for(int k = 0; k < direct.length; k++) {
                int newRow = row + direct[k][0];
                int newCol = col + direct[k][1];

                if(board[newRow][newCol]=='O' && !flags.contains(new Pair<>(newRow, newCol))) {
                    queue.addLast(new Pair<>(newRow, newCol));
                    flags.add(new Pair<>(newRow, newCol));
                    zeroNodes.addLast(new Pair<>(newRow, newCol));
                }
            }
        }

        while(!zeroNodes.isEmpty()) {
            Pair<Integer, Integer> node2 = zeroNodes.pollFirst();
            board[node2.getKey()][node2.getValue()] = 'X';
        }
        return;
    }

3)离建筑最近距离

image
【解题思路】:
可以考虑分别以三个建筑(三个为1的点),分别BFS方式查找到所有空地0的深度。三次BFS后,获得:

  1. 每个空地 与建筑物 [0,0]、[0, 4]、[2,2]的距离。总中找到到三个建筑物距离和最小的点。
   public static int solve(int[][] board) {
        if(board.length == 0 || board[0].length == 0) {
            return 0;
        }

        int[][] distForZreo = new int[board.length][board[0].length];

        //遍历每个1节点,计算其他0节点到其的距离,距离值累加入distForZreo中保存
        for(int i = 0; i<board.length; i++) {
            for(int j=0; j <board[0].length; j++) {
                if(board[i][j] == 1) {
                    bfsFineZero(board, i, j, distForZreo);
                }
            }
        }

        //遍历distForZreo中记录的所有0节点到三个1节点的累加值,找最小值
        int minDistance = Integer.MAX_VALUE;
        for(int k = 0; k<board.length; k++) {
            for (int l = 0; l < board[0].length; l++) {
                if (distForZreo[k][l] != 0) {
                    minDistance = Math.min(minDistance, distForZreo[k][l]);
                }
            }
        }

        return minDistance;
    }

    public static void bfsFineZero(int[][] board, int rowIndex, int colIndex, int[][] distForZreo) {
        //定义queue节点结构,记录为0的二威数组单元所以、及其到根节点1的距离
        class queueNode {
          int rowIdx;
          int colIdx;
          int distance;

          public queueNode(int row, int col, int distance) {
              this.rowIdx = row;
              this.colIdx = col;
              this.distance = distance;
          }
        };

        //定义队列,需要查找距离,搜易需要记录遍历的节点的深度
        ArrayDeque<queueNode> queue = new ArrayDeque<queueNode>();

        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        HashSet<Pair<Integer, Integer>> flags = new HashSet<Pair<Integer, Integer>>();

        //定义数组root关联节点的方向
        int[][] direct = new int[][]{{0, -1}, {0 ,1}, {-1, 0}, {1, 0}};

        queue.addLast(new queueNode(rowIndex,colIndex,0));
        flags.add(new Pair<>(rowIndex, colIndex));

        //{1,0,2,0,1},
        //{0,0,0,0,0},
        //{0,0,1,0,0}
        while(!queue.isEmpty()) {
            queueNode node = queue.pollFirst();
            int row = node.rowIdx;
            int col = node.colIdx;

            //如果是0节点,保存其到root(1节点)的距离
            if(board[row][col] == 0) {
                distForZreo[row][col] += node.distance;
            }

            //遍历4个方向,如果是0节点,则入栈,因为是可达的。1/2接线不可通过,不需再向下找
            for(int k = 0; k < direct.length; k++) {
                int newRow = row + direct[k][0];
                int newCol = col + direct[k][1];

                if(newRow >=0 && newRow < board.length && newCol >=0 && newCol < board[0].length
                    && board[newRow][newCol] == 0  && !flags.contains(new Pair<>(newRow, newCol))) {
                    queue.addLast(new queueNode(newRow,newCol,node.distance + 1));
                    flags.add(new Pair<>(newRow, newCol));
                }
            }
        }
    }

4)迷宫(力扣)☆☆☆☆☆

在迷宫中有一个球,里面有空的空间和墙壁。球可以通过滚上,下,左或右移动,

给定球的起始位置,目的地和迷宫,找出让球停到目的地的最短距离。距离为求从起始位置到目标位置经过的空地个数。

迷宫由二维数组表示。1表示墙和0表示空的空间。你可以假设迷宫的边界都是墙。开始和目标坐标用行和列索引表示。

输入:
map =
[
[0,0,1,0,0],
[0,0,0,0,0],
[0,0,0,1,0],
[1,1,0,1,1],
[0,0,0,0,0]
]
start = [0,4]
end = [3,2]
输出:6

【思路分析】:
以起点为根,BFS方式找目标节点:

  • 找到,返回前一节点Distance + 1。
  • 循环完毕,找不到,返回-1 。

代码参考:

   public static int solve(int[][] board, int startRow, int startCol, int endRow, int endCol) {
        if(board.length == 0 || board[0].length == 0
         || startRow < 0 || startCol < 0
         || startCol >= board.length || endCol >= board.length) {
            return 0;
        }

        //定义queue节点结构,记录为0的二威数组单元所以、及其到根节点1的距离
        class queueNode {
            int rowIdx;
            int colIdx;
            int distance;

            public queueNode(int row, int col, int distance) {
                this.rowIdx = row;
                this.colIdx = col;
                this.distance = distance;
            }
        };

        //定义队列,需要查找距离,搜易需要记录遍历的节点的深度
        ArrayDeque<queueNode> queue = new ArrayDeque<queueNode>();

        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        HashSet<Pair<Integer, Integer>> flags = new HashSet<Pair<Integer, Integer>>();

        //定义数组root关联节点的方向
        int[][] direct = new int[][]{{0, -1}, {0 ,1}, {-1, 0}, {1, 0}};

        queue.addLast(new queueNode(startRow,startCol,0));
        flags.add(new Pair<>(startRow, startCol));

        while(!queue.isEmpty()) {
            queueNode node = queue.pollFirst();
            int row = node.rowIdx;
            int col = node.colIdx;

            //遍历4个方向,如果是0节点,则入栈,因为是可达的。1/2接线不可通过,不需再向下找
            for(int k = 0; k < direct.length; k++) {
                int newRow = row + direct[k][0];
                int newCol = col + direct[k][1];

                if(newRow >=0 && newRow < board.length && newCol >=0 && newCol < board[0].length) {
                    if(newRow == endRow && newCol == endCol) {
                        return node.distance + 1;
                    }

                    if(board[newRow][newCol] == 0  && !flags.contains(new Pair<>(newRow, newCol))) {
                        queue.addLast(new queueNode(newRow, newCol, node.distance + 1));
                        flags.add(new Pair<>(newRow, newCol));
                    }
                }
            }
        }

        return -1;
    }

5)扫雷游戏

让我们一起来玩扫雷游戏!
给定一个代表游戏板的二维字符矩阵。 'M' 代表一个未挖出的地雷,'E' 代表一个未挖出的空方块,'B' 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的已挖出的空白方块,数字('1' 到 '8')表示有多少地雷与这块已挖出的方块相邻,'X' 则表示一个已挖出的地雷。

现在给出在所有未挖出的方块中('M'或者'E')的下一个点击位置(行和列索引),根据以下规则,返回相应位置被点击后对应的面板:

如果一个地雷('M')被挖出,游戏就结束了- 把它改为 'X'。
如果一个没有相邻地雷的空方块('E')被挖出,修改它为('B'),并且所有和其相邻的未挖出方块都应该被递归地揭露。
如果一个至少与一个地雷相邻的空方块('E')被挖出,修改它为数字('1'到'8'),表示相邻地雷的数量。
如果在此次点击中,若无更多方块可被揭露,则返回面板。

示例 1:

输入:

[['E', 'E', 'E', 'E', 'E'],
['E', 'E', 'M', 'E', 'E'],
['E', 'E', 'E', 'E', 'E'],
['E', 'E', 'E', 'E', 'E']]
Click : [3,0]

输出:
[['B', '1', 'E', '1', 'B'],
['B', '1', 'M', '1', 'B'],
['B', '1', '1', '1', 'B'],
['B', 'B', 'B', 'B', 'B']]

示例 2:
输入:
[['B', '1', 'E', '1', 'B'],
['B', '1', 'M', '1', 'B'],
['B', '1', '1', '1', 'B'],
['B', 'B', 'B', 'B', 'B']]
Click : [1,2]
输出:
[['B', '1', 'E', '1', 'B'],
['B', '1', 'X', '1', 'B'],
['B', '1', '1', '1', 'B'],
['B', 'B', 'B', 'B', 'B']]

注意:

输入矩阵的宽和高的范围为 [1,50]。
点击的位置只能是未被挖出的方块 ('M' 或者 'E'),这也意味着面板至少包含一个可点击的方块。
输入面板不会是游戏结束的状态(即有地雷已被挖出)。
简单起见,未提及的规则在这个问题中可被忽略。例如,当游戏结束时你不需要挖出所有地雷,考虑所有你可能赢得游戏或标记方块的情况。

【分析思路】:

  1. 起始坐标是M,直接设置X,返回。
  2. 起始坐标不是M,根据指定点BFS递归
    • 单开当前节点,遍历周边八个方向节点设置后的值,
      • 如果周边全非M,则点开的值为B;B周边节点不可能是炸弹,可以继续加入队列点开。
      • 如果周边存在M,则点开的值为炸点数量;炸弹数量周边的节点不知道具体哪个是炸点,不敢点开,不加入队里
   public static void solve(char[][] board, int startRow, int startCol) {
        if(board.length == 0 || board[0].length == 0
         || startRow < 0 || startRow >= board.length
         || startCol < 0 || startCol >= board[0].length) {
            return;
        }

        if(board[startRow][startCol] == 'M') {
            board[startRow][startCol] = 'X';
            return;
        }

        //定义队列,需要查找距离,搜易需要记录遍历的节点的深度
        ArrayDeque<Pair<Integer, Integer>> queue = new ArrayDeque<Pair<Integer, Integer>>();

        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        HashSet<Pair<Integer, Integer>> flags = new HashSet<Pair<Integer, Integer>>();

        //定义数组root关联节点的方向
        int[][] direct = new int[][]{{0, -1}, {0 ,1}, {-1, 0}, {1, 0}, {-1, -1},{-1, 1} ,{1, -1} ,{1, 1}};

        queue.addLast(new Pair<>(startRow,startCol));
        flags.add(new Pair<>(startRow, startCol));

        while(!queue.isEmpty()) {
            Pair node = queue.pollFirst();

            int row = (Integer)node.getKey();
            int col = (Integer)node.getValue();

            HashSet<Pair<Integer,Integer>> tempSet = new HashSet<Pair<Integer,Integer>>();

            int minesNum = 0;
            //遍历8个方向,如果是0节点,则入栈,因为是可达的。1/2接线不可通过,不需再向下找
            for(int k = 0; k < direct.length; k++) {
                int newRow = row + direct[k][0];
                int newCol = col + direct[k][1];

                if(newRow >=0 && newRow < board.length && newCol >=0 && newCol < board[0].length) {
                    if (board[newRow][newCol] == 'M') {
                        minesNum++;
                        break;
                    }
                    else if(!flags.contains(new Pair<>(newRow, newCol))) {
                        tempSet.add(new Pair<>(newRow, newCol));
                    }
                }
            }

            if(minesNum == 0) {
                board[row][col] = 'B';
                queue.addAll(tempSet);
                flags.addAll (tempSet);
                tempSet.clear();
            }
            else {
                board[row][col] = (char) (minesNum + '0');
            }
        }

        return;
    }

6)推箱子

「推箱子」是一款风靡全球的益智小游戏,玩家需要将箱子推到仓库中的目标位置。
游戏地图用大小为 n * m 的网格 grid 表示,其中每个元素可以是墙、地板或者是箱子。
现在你将作为玩家参与游戏,按规则将箱子 'B' 移动到目标位置 'T' :
玩家用字符 'S' 表示,只要他在地板上,就可以在网格中向上、下、左、右四个方向移动。
地板用字符 '.' 表示,意味着可以自由行走。
墙用字符 '#' 表示,意味着障碍物,不能通行。 
箱子仅有一个,用字符 'B' 表示。相应地,网格上有一个目标位置 'T'。
玩家需要站在箱子旁边,然后沿着箱子的方向进行移动,此时箱子会被移动到相邻的地板单元格。记作一次「推动」。
玩家无法越过箱子。
返回将箱子推到目标位置的最小 推动 次数,如果无法做到,请返回 -1。

示例 1:
输入:grid = [["#","#","#","#","#","#"],
["#","T","#","#","#","#"],
  ["#",".",".","B",".","#"],
  ["#",".","#","#",".","#"],
  ["#",".",".",".","S","#"],
  ["#","#","#","#","#","#"]]
输出:3
解释:我们只需要返回推箱子的次数。
示例 2:

输入:grid = [["#","#","#","#","#","#"],
["#","T","#","#","#","#"],
  ["#",".",".","B",".","#"],
  ["#","#","#","#",".","#"],
  ["#",".",".",".","S","#"],
  ["#","#","#","#","#","#"]]
输出:-1
示例 3:

输入:grid = [["#","#","#","#","#","#"],
  ["#","T",".",".","#","#"],
  ["#",".","#","B",".","#"],
  ["#",".",".",".",".","#"],
  ["#",".",".",".","S","#"],
  ["#","#","#","#","#","#"]]
输出:5
解释:向下、向左、向左、向上再向上。
示例 4:

输入:grid = [["#","#","#","#","#","#","#"],
  ["#","S","#",".","B","T","#"],
  ["#","#","#","#","#","#","#"]]
输出:-1

提示:

1 <= grid.length <= 20
1 <= grid[i].length <= 20
grid 仅包含字符 '.', '#',  'S' , 'T', 以及 'B'。
grid 中 'S', 'B' 和 'T' 各只能出现一个。

【思路】:

  1. 箱子B能到目标T。
  2. 人S 能到B的周边位置。

So,两次BFS:

  1. 第一次BFS,计算人能到的位置全集(包含T、B所在位置),记录下来。

  2. 第二次BFS,计算B能到F的可行路径:

    此时在逐层遍历中,箱子可达下层节点的条件是:

    • 目标节点非墙,目标节点的对称节点人能达到。
      image
    • 为了避免如下情况,判断对称节点可达时:除了判断对称节点在人可达集中外,同时判断外围的三个节点至少一个在人可达集合内。
      image

代码参考:

   public static int bfs(char[][] board, int[] box, int[] persion, int[] dest) {
        //记录人可达集合
        class Node {
            int rowIndex;
            int colIndex;
            int dest;

            public Node(int row, int col, int dest) {
                this.rowIndex = row;
                this.colIndex = col;
                this.dest = dest;
            }
        }

        //定义队列,需要查找距离,搜易需要记录遍历的节点的深度
        ArrayDeque<Node> queue = new ArrayDeque<Node>();

        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        HashSet<Pair<Integer, Integer>> arrivedNodes = new HashSet<Pair<Integer, Integer>>();

        //定义数组root关联节点的方向
        int[][] direct = new int[][]{{0, -1}, {0 ,1}, {-1, 0}, {1, 0}};

        queue.addLast(new Node(persion[0], persion[1], 0));
        arrivedNodes.add(new Pair<>(persion[0], persion[1]));
        while(!queue.isEmpty()) {
            Node node = queue.pollFirst();

            int row = node.rowIndex;
            int col = node.colIndex;

            //遍历4个方向,如果是0节点,则入栈,因为是可达的。1/2接线不可通过,不需再向下找
            for(int k = 0; k < direct.length; k++) {
                int newRow = row + direct[k][0];
                int newCol = col + direct[k][1];

                if(newRow >=0 && newRow < board.length && newCol >=0 && newCol < board[0].length) {
                    if ((board[newRow][newCol] == '.' || board[newRow][newCol] == 'T' || board[newRow][newCol] == 'B') && !arrivedNodes.contains(new Pair<>(newRow, newCol))) {
                        queue.addLast(new Node(newRow, newCol, 0));
                        arrivedNodes.add(new Pair<>(newRow, newCol));
                    }
                }
            }
        }

        //请空queue,用于下一步记录箱子
        queue.clear();

        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        HashSet<Pair<Integer, Integer>> flags = new HashSet<Pair<Integer, Integer>>();

        queue.addLast(new Node(box[0], box[1], 0));
        flags.add(new Pair<>(box[0], box[1]));

        while(!queue.isEmpty()) {
            Node node = queue.pollFirst();

            int row1 = node.rowIndex;
            int col1 = node.colIndex;

            if (row1 == dest[0] && col1 == dest[1]) {
                return node.dest;
            }

            int leftNodeRow = row1;
            int leftNodeCol = col1-1;

            int rightNodeRow = row1;
            int rightNodeCol = col1+1;

            int upNodeRow = row1-1;
            int upNodeCol = col1;

            int downNodeRow = row1+1;
            int downNodeCol = col1;

            if(arrivedNodes.contains(new Pair<>(leftNodeRow, leftNodeCol)) && arrivedNodes.contains(new Pair<>(rightNodeRow, rightNodeCol))) {
                if((arrivedNodes.contains(new Pair<>(leftNodeRow-1, leftNodeCol))
                        || arrivedNodes.contains(new Pair<>(leftNodeRow+1, leftNodeCol))
                        || arrivedNodes.contains(new Pair<>(leftNodeRow, leftNodeCol-1)))
                        && !flags.contains(new Pair<>(rightNodeRow, rightNodeCol))) {

                    queue.addLast(new Node(rightNodeRow, rightNodeCol, node.dest +1));
                    flags.add(new Pair<>(rightNodeRow, rightNodeCol));
                }

                if((arrivedNodes.contains(new Pair<>(rightNodeRow-1, rightNodeCol))
                        || arrivedNodes.contains(new Pair<>(rightNodeRow+1, rightNodeCol))
                        || arrivedNodes.contains(new Pair<>(rightNodeRow, rightNodeCol+1)))
                        && !flags.contains(new Pair<>(leftNodeRow, leftNodeCol))) {
                    queue.addLast(new Node(leftNodeRow, leftNodeCol, node.dest +1));
                    flags.add(new Pair<>(leftNodeRow, leftNodeCol));
                }
            }

            if(arrivedNodes.contains(new Pair<>(upNodeRow, upNodeCol)) && arrivedNodes.contains(new Pair<>(downNodeRow, downNodeCol))) {
                if((arrivedNodes.contains(new Pair<>(upNodeRow-1, upNodeCol))
                        || arrivedNodes.contains(new Pair<>(upNodeRow, upNodeCol-1))
                        || arrivedNodes.contains(new Pair<>(upNodeRow, upNodeCol+1)))
                        && !flags.contains(new Pair<>(downNodeRow, downNodeCol))) {

                    queue.addLast(new Node(downNodeRow, downNodeCol, node.dest +1));
                    flags.add(new Pair<>(downNodeRow, downNodeCol));
                }

                if((arrivedNodes.contains(new Pair<>(downNodeRow-1, downNodeCol))
                        || arrivedNodes.contains(new Pair<>(downNodeRow, downNodeCol-1))
                        || arrivedNodes.contains(new Pair<>(downNodeRow, downNodeCol+1)))
                        && !flags.contains(new Pair<>(upNodeRow, upNodeCol))) {
                    queue.addLast(new Node(upNodeRow, upNodeCol, node.dest +1));
                    flags.add(new Pair<>(upNodeRow, upNodeCol));
                }
            }
        }
        return -1;
    }

结果,其他都对,如下情况下:

返回了5,预期结果是7:
image
原因是箱子在红点时,人过不去了,要先把箱子推出去。
image
So,人的可达点是变化的,不能采用如上先遍历所有可达点、缓存的方式。

【优化】:每次根据箱子预期位置,动态计算人是否可达

    public static boolean persionArrived(char[][] board, int[]cur, int[] dest) {
        //定义队列,需要查找距离,搜易需要记录遍历的节点的深度
        ArrayDeque<Pair<Integer, Integer>> queue = new ArrayDeque<Pair<Integer, Integer>>();

        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        HashSet<Pair<Integer, Integer>> flags = new HashSet<Pair<Integer, Integer>>();

        //定义数组root关联节点的方向
        int[][] direct = new int[][]{{0, -1}, {0 ,1}, {-1, 0}, {1, 0}};
        //Pair tmpPair = new Pair<>(cur[0],cur[1]);
        queue.addLast(new Pair<>(cur[0],cur[1]));
        flags.add(new Pair<>(cur[0],cur[1]));

        while(!queue.isEmpty()) {
            Pair node = queue.pollFirst();

            int row = (Integer)node.getKey();
            int col = (Integer)node.getValue();

            if(row == dest[0] && col == dest[1])
            {
                return true;
            }

            //遍历4个方向,如果是0节点,则入栈,因为是可达的。1/2接线不可通过,不需再向下找
            for(int k = 0; k < direct.length; k++) {
                int newRow = row + direct[k][0];
                int newCol = col + direct[k][1];

                if(newRow >=0 && newRow < board.length
                    && newCol >=0 && newCol < board[0].length) {
                    if ((board[newRow][newCol] == '.' || board[newRow][newCol] == 'T') && !flags.contains(new Pair<>(newRow, newCol))) {
                        queue.addLast(new Pair<>(newRow, newCol));
                        flags.add(new Pair<>(newRow, newCol));
                    }
                }
            }
        }
        return false;
    }

    public static int bfs(char[][] board, int[] box, int[] persion, int[] dest) {
        //记录人可达集合
        class Node {
            int rowIndex;
            int colIndex;
            int dest;

            public Node(int row, int col, int dest) {
                this.rowIndex = row;
                this.colIndex = col;
                this.dest = dest;
            }
        }

        //定义队列,需要查找距离,搜易需要记录遍历的节点的深度
        ArrayDeque<Node> queue = new ArrayDeque<Node>();
        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        HashSet<Pair<Integer, Integer>> flags = new HashSet<Pair<Integer, Integer>>();

        //定义数组root关联节点的方向,互为相反方向
        int[][] direct = new int[][]{{0, -1}, {0 ,1}, {-1, 0}, {1, 0}};
        int[][] directX = new int[][]{{0, 1}, {0 ,-1}, {1, 0}, {-1, 0}};

        queue.addLast(new Node(box[0], box[1], 0));
        flags.add(new Pair<>(box[0], box[1]));

        while(!queue.isEmpty()) {
            Node node = queue.pollFirst();

            int row1 = node.rowIndex;
            int col1 = node.colIndex;

            if (row1 == dest[0] && col1 == dest[1]) {
                return node.dest;
            }

            //推导箱子在此位置
            board[row1][col1] = 'B';

            for(int i = 0; i < direct.length; i++) {
                int newRow = row1 + direct[i][0];
                int newCol = col1 + direct[i][1];

                int persionRow = row1 + directX[i][0];
                int persionCol = col1 + directX[i][1];

                if(newRow >0 && newRow < board.length
                    && newCol >0 && newRow < board[0].length
                    && persionRow >0 && persionRow < board.length
                    && persionCol >0 && persionCol < board[0].length) {

                    int[] dst = new int[]{persionRow, persionCol};

                    if((board[newRow][newCol] == '.' || board[newRow][newCol] == 'T' )
                        && persionArrived(board, persion, dst)
                        && !flags.contains(new Pair<>(newRow, newCol))) {
                        queue.addLast(new Node(newRow, newCol, node.dest +1));
                        flags.add(new Pair<>(newRow, newCol));
                    }
                }

            }

            //清空推导中临时的箱子位置
            board[row1][col1] = '.';
        }
        return -1;
    }

结果:

如下问题,绿色点需要走两次,但是通过flags标识后,导致[4]/[4]->[3]/[5]无法走。

  • 如果不打标签,会导致死循环。
    image

【优化】:
标签中加入经过一个Node的方向,同一方向不允许重复。
仍然报错,如下:当箱子在绿点,S位置到紫点不可达,导致[2]/[4]位置无效,所以要记录上次Persion的位置。
image

    public static int bfs(char[][] board, int[] box, int[] persion, int[] dest) {
        //记录人可达集合
        class Node {
            int rowIndex;
            int colIndex;
            int dest;

            public Node(int row, int col, int dest) {
                this.rowIndex = row;
                this.colIndex = col;
                this.dest = dest;
            }
        }

        //定义队列,需要查找距离,搜易需要记录遍历的节点的深度
        ArrayDeque<Node> queue = new ArrayDeque<Node>();

        //定义数组root关联节点的方向,互为相反方向
        int[][] direct = new int[][]{ {-1, 0}, {1, 0},{0, -1}, {0 ,1},};
        int[][] directX = new int[][]{{1, 0}, {-1, 0},{0, 1}, {0 ,-1},};
        int[][] count = new int[board.length][board[0].length];

        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        //HashSet<Pair<Integer, Integer>> flags = new HashSet<Pair<Integer, Integer>>();
        boolean[][][] flags = new boolean[direct.length][board.length][board[0].length];

        queue.addLast(new Node(box[0], box[1], 0));
        //flags.add(new Pair<>(box[0], box[1]));
        flags[0][box[0]][box[1]] = true;
        flags[1][box[0]][box[1]] = true;
        flags[2][box[0]][box[1]] = true;
        flags[3][box[0]][box[1]] = true;

        while(!queue.isEmpty()) {
            Node node = queue.pollFirst();

            int row1 = node.rowIndex;
            int col1 = node.colIndex;

            if (row1 == dest[0] && col1 == dest[1]) {
                return node.dest;
            }

            //推导箱子在此位置
            board[row1][col1] = 'B';

            if(row1 ==3 && col1 ==4) {
                row1 =3;
            }

            if(row1 ==3 && col1 ==3) {
                row1 =3;
            }

            for(int i = 0; i < direct.length; i++) {
                int newRow = row1 + direct[i][0];
                int newCol = col1 + direct[i][1];

                int persionRow = row1 + directX[i][0];
                int persionCol = col1 + directX[i][1];

                if(newRow >0 && newRow < board.length
                    && newCol >0 && newRow < board[0].length
                    && persionRow >0 && persionRow < board.length
                    && persionCol >0 && persionCol < board[0].length) {

                    int[] dst = new int[]{persionRow, persionCol};

                    if((board[newRow][newCol] == '.' || board[newRow][newCol] == 'T')
                        && persionArrived(board, persion, dst)
                        && flags[i][newRow][newCol] != true) {
                        queue.addLast(new Node(newRow, newCol, node.dest +1));
                        flags[i][newRow][newCol] = true;
                    }
                }

            }

            //清空推导中临时的箱子位置
            board[row1][col1] = '.';
        }
        return -1;
    }

【优化】:入队列,除了记录节点,还需要记录当前Persion所在位置。

    public static boolean persionArrived(char[][] board, int curPersonRow, int curPersonCol, int destRow, int destCol) {
        //定义队列,需要查找距离,搜易需要记录遍历的节点的深度
        ArrayDeque<Pair<Integer, Integer>> queue = new ArrayDeque<Pair<Integer, Integer>>();

        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        HashSet<Pair<Integer, Integer>> flags = new HashSet<Pair<Integer, Integer>>();

        //定义数组root关联节点的方向
        int[][] direct = new int[][]{{0, -1}, {0 ,1}, {-1, 0}, {1, 0}};
        //Pair tmpPair = new Pair<>(cur[0],cur[1]);
        queue.addLast(new Pair<>(curPersonRow,curPersonCol));
        flags.add(new Pair<>(curPersonRow,curPersonCol));

        while(!queue.isEmpty()) {
            Pair node = queue.pollFirst();

            int row = (Integer)node.getKey();
            int col = (Integer)node.getValue();

            if(row == destRow && col == destCol)
            {
                return true;
            }

            //遍历4个方向,如果是0节点,则入栈,因为是可达的。1/2接线不可通过,不需再向下找
            for(int k = 0; k < direct.length; k++) {
                int newRow = row + direct[k][0];
                int newCol = col + direct[k][1];

                if(newRow >=0 && newRow < board.length
                    && newCol >=0 && newCol < board[0].length) {
                    if (board[newRow][newCol] != '#' && board[newRow][newCol] != 'B' && !flags.contains(new Pair<>(newRow, newCol))) {
                        queue.addLast(new Pair<>(newRow, newCol));
                        flags.add(new Pair<>(newRow, newCol));
                    }
                }
            }
        }
        return false;
    }

    public static int bfs(char[][] board, int[] box, int[] persion, int[] dest) {
        //记录人可达集合
        class Node {
            int rowIndex;
            int colIndex;
            int dest;
            int curPersonRow;
            int curPersonCol;

            public Node(int row, int col, int dest, int curPersonRow, int curPersonCol) {
                this.rowIndex = row;
                this.colIndex = col;
                this.dest = dest;
                this.curPersonRow = curPersonRow;
                this.curPersonCol = curPersonCol;
            }
        }

        //定义队列,需要查找距离,搜易需要记录遍历的节点的深度
        ArrayDeque<Node> queue = new ArrayDeque<Node>();

        //定义数组root关联节点的方向,互为相反方向
        int[][] direct = new int[][]{ {-1, 0}, {1, 0},{0, -1}, {0 ,1},};
        int[][] directX = new int[][]{{1, 0}, {-1, 0},{0, 1}, {0 ,-1},};

        //定义标志数组,找过的,不需要重复找。避免两个相邻的0节点互相找
        boolean[][][] flags = new boolean[direct.length][board.length][board[0].length];

        queue.addLast(new Node(box[0], box[1], 0, persion[0], persion[1]));
        flags[0][box[0]][box[1]] = true;
        flags[1][box[0]][box[1]] = true;
        flags[2][box[0]][box[1]] = true;
        flags[3][box[0]][box[1]] = true;

        while(!queue.isEmpty()) {
            Node node = queue.pollFirst();

            int row1 = node.rowIndex;
            int col1 = node.colIndex;
            int curPersonRow = node.curPersonRow;
            int curPersonCol = node.curPersonCol;

            if (row1 == dest[0] && col1 == dest[1]) {
                return node.dest;
            }

            //推导箱子在此位置
            board[row1][col1] = 'B';
            for(int i = 0; i < direct.length; i++) {
                int newRow = row1 + direct[i][0];
                int newCol = col1 + direct[i][1];

                int personRow = row1 + directX[i][0];
                int personCol = col1 + directX[i][1];

                if(newRow >=0 && newRow < board.length
                    && newCol >=0 && newCol < board[0].length
                    && personRow >=0 && personRow < board.length
                    && personCol >=0 && personCol < board[0].length) {

                    if(board[newRow][newCol] != '#'
                        && persionArrived(board, curPersonRow, curPersonCol, personRow, personCol)
                        && flags[i][newRow][newCol] != true) {
                        queue.addLast(new Node(newRow, newCol, node.dest +1, row1, col1));
                        flags[i][newRow][newCol] = true;
                    }
                }

            }

            //清空推导中临时的箱子位置
            board[row1][col1] = '.';
        }
        return -1;
    }

7)进击的骑士(力扣)

image

提示:

  • |x| + |y| <= 300

【分析】:

本地是的典型的BFS,从启动找终点最短路径:、

  1. 找下一级搜索节点,有8种类型[-1. -2]、[-1,+2]、[-2, -1]、[-2, +1]、[+1. -2]、[+1,+2]、[+2, -1]、[+2, +1]
  2. 原点是[0, 0],输入是目标节点。
  3. 如x=2或-2,则包含x节点的最小矩阵横坐标为-2/-1/0/1/2。推算:
    收到x、y,定义矩阵大小为[2abs(x)+1] | [2abs(y)+1]。
    输入的坐标全部按照横坐标+|x|,纵坐标+|y| 换算:骑士原点|x||y|、目标点x+|x|、y+|y|

8)公交路线

题目描述:
我们有一系列公交路线。每一条路线 routes[i] 上都有一辆公交车在上面循环行驶。例如,有一条路线 routes[0] = [1, 5, 7],表示第一辆 (下标为0) 公交车会一直按照 1->5->7->1->5->7->1->... 的车站路线行驶。
假设我们从 S 车站开始(初始时不在公交车上),要去往 T 站。 期间仅可乘坐公交车,求出最少乘坐的公交车数量。返回 -1 表示不可能到达终点车站。

【分析】
通过例子分析,假设有四辆公交:{{1,5,7}, {2,5,6}, {2,3,4}, {9,7,8},需要从7站点到4站点,可能得检索树为:
image

可以分析得出:

  1. 组成站点树
  2. BFS的下一层的检索关系,是经过当前站点的公交所经由的所有站点。
  3. 检索的换栈个数,就是树的检索深度。(root的深度是0)

输入是公交序列,如何快速的根据站点找到经过站点的公交序列呢?
image

源码参考:

   public static int numBusesToDestination(int[][] routes, int source, int target) {
        if(routes.length ==0 || routes[0].length ==0) {
            return -1;
        }

        HashMap<Integer, ArrayList<Integer>> map = new HashMap<Integer, ArrayList<Integer>>();
        for(int i=0; i < routes.length; i++) {
            for(int j=0; j< routes[i].length;j++) {
                if(!map.containsKey(routes[i][j])) {
                    map.put(routes[i][j], new ArrayList<>(Arrays.asList(i)));
                }
                else {
                    map.get(routes[i][j]).add(i);
                }
            }
        }

        if(!map.containsKey(source) || !map.containsKey(target)) {
            return -1;
        }

        ArrayDeque<Pair<Integer, Integer>> queue = new ArrayDeque<Pair<Integer, Integer>>();
        HashSet<Integer> flags = new HashSet<Integer>();

        queue.addLast(new Pair<>(source, 0));
        flags.add(source);

        while(!queue.isEmpty()) {
            Pair node = queue.pollFirst();
            if((Integer) node.getKey() == target) {
                return (Integer)node.getValue();
            }

            ArrayList<Integer> list = map.get(node.getKey());
            if(list == null) {
                continue;
            }

            for(Integer index: list) {
                if(index < routes.length) {
                    for(int k =0; k< routes[index].length; k++) {
                        if(!flags.contains(routes[index][k])) {
                              queue.add(new Pair<>(routes[index][k], (Integer)node.getValue()+1));
                            flags.add(routes[index][k]);
                        }
                    }
                }
            }
        }
        return -1;
    }

9)最短的桥

在给定的二维二进制数组 A 中,存在两座岛。(岛是由四面相连的 1 形成的一个最大组。)
现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。
返回必须翻转的 0 的最小数目。(可以保证答案至少是 1 。)

示例 1:
输入:A = [[0,1],[1,0]]
输出:1

示例 2:
输入:A = [[0,1,0],[0,0,0],[0,0,1]]
输出:2

示例 3:
输入:A = [[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
输出:1

提示:
2 <= A.length == A[0].length <= 100
A[i][j] == 0 或 A[i][j] == 1

【思路】
我们通过对数组 A 中的 1 进行搜索,可以得到两座岛的位置集合,分别为 source 和 target。随后我们从 source 中的所有位置开始进行广度优先搜索,当它们到达了target中的任意一个位置时,搜索的层数就是答案。

So:

  1. 合并集,找打两个岛屿集合。
  2. 从一个集合“所有节点”开发BFS,知道目标节点在集合2中

注意:

  1. 设计到频繁检索,注意集合的深度问题。
  2. 初始如队列,可把Source岛屿的所有节点初始都加入到队列,Dept为0。找到的第一个另外岛屿节点,则Dept就最小。

力扣官方代码参考:

    class Solution {
        public int shortestBridge(int[][] A) {
            int R = A.length, C = A[0].length;
            int[][] colors = getComponents(A);

            Queue<Node> queue = new LinkedList();
            Set<Integer> target = new HashSet();

            //集合1所有节点入BFS队里,集合2所有几点入Target集合。
            for (int r = 0; r < R; ++r)
                for (int c = 0; c < C; ++c) {
                    if (colors[r][c] == 1) {
                        queue.add(new Node(r, c, 0));
                    } else if (colors[r][c] == 2) {
                        target.add(r * C + c);
                    }
                }
            }

            while (!queue.isEmpty()) {
                Node node = queue.poll();
                if (target.contains(node.r * C + node.c))
                    return node.depth - 1;
                for (int nei: neighbors(A, node.r, node.c)) {
                    int nr = nei / C, nc = nei % C;
                    if (colors[nr][nc] != 1) {
                        queue.add(new Node(nr, nc, node.depth + 1));
                        colors[nr][nc] = 1;
                    }
                }
            }

            throw null;
        }

        //遍历连个集合,第一个集合设置为1,第二个集合设置为2。
        public int[][] getComponents(int[][] A) {
            int R = A.length, C = A[0].length;
            int[][] colors = new int[R][C];
            int t = 0;

            for (int r0 = 0; r0 < R; ++r0)
                for (int c0 = 0; c0 < C; ++c0)
                    if (colors[r0][c0] == 0 && A[r0][c0] == 1) {
                        // Start dfs
                        Stack<Integer> stack = new Stack();
                        stack.push(r0 * C + c0);
                        colors[r0][c0] = ++t;

                        //上下左右,向外扩散,相连的设置t值 (第一个集合为1,第二个集合为2)
                        while (!stack.isEmpty()) {
                            int node = stack.pop();
                            int r = node / C, c = node % C;
                            for (int nei: neighbors(A, r, c)) {
                                int nr = nei / C, nc = nei % C;
                                if (A[nr][nc] == 1 && colors[nr][nc] == 0) {
                                    colors[nr][nc] = t;
                                    stack.push(nr * C + nc);
                                }
                            }
                        }
                    }

            return colors;
        }

        public List<Integer> neighbors(int[][] A, int r, int c) {
            int R = A.length, C = A[0].length;
            List<Integer> ans = new ArrayList();
            if (0 <= r-1) ans.add((r-1) * R + c);
            if (0 <= c-1) ans.add(r * R + (c-1));
            if (r+1 < R) ans.add((r+1) * R + c);
            if (c+1 < C) ans.add(r * R + (c+1));
            return ans;
        }
    }

10)腐烂的橘子

在给定的网格中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。
返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。

示例 1:
输入:[[2,1,1],[1,1,0],[0,1,1]]
输出:4

【思路】---容易
BFS,以2为起点Node找橘子,找到了就腐烂,继续扩散寻找

  1. 以2为起点Node。
  2. 找Node的上、下、左、右;为1,则设置其值为2,并入队列;为0,则跳过。
  3. 直到队里处理完毕。
  4. 由于如果有橘子不会腐烂,返回-1。So,可以先遍历好的橘子总述,最终腐烂数和总数比较。
posted @ 2021-07-22 18:55  小拙  阅读(209)  评论(0编辑  收藏  举报