【LeetCode 】试题总结:广度优先搜索(BFS)

说明:本文仅做为本人总结算法竞赛试题的笔记,参照许多了题解,如有侵权请联系。

一、数据结构:二叉树中的 BFS

(一)、二叉树的堂兄弟节点

在二叉树中,根节点位于深度 0 处,每个深度为 k 的节点的子节点位于深度 k+1 处。

如果二叉树的两个节点深度相同,但 父节点不同 ,则它们是一对堂兄弟节点。

我们给出了具有唯一值的二叉树的根节点 root ,以及树中两个不同节点的值 x 和 y 。

只有与值 x 和 y 对应的节点是堂兄弟节点时,才返回 true 。否则,返回 false。

示例 1:

在这里插入图片描述

输入:root = [1,2,3,4], x = 4, y = 3
输出:false

示例 2:

在这里插入图片描述

输入:root = [1,2,3,null,4,null,5], x = 5, y = 4
输出:true
输入:root = [1,2,3,null,4], x = 2, y = 3
输出:false

提示:

二叉树的节点数介于 2 到 100 之间。
每个节点的值都是唯一的、范围为 1 到 100 的整数。
试题链接

https://leetcode-cn.com/problems/cousins-in-binary-tree/

解题思路

这道题虽然标记为bfs,但看到使用bfs的好像并不多啊。个人认为对于这题来说bfs还更加好理解一点。
树里面的bfs就是层序遍历。
那么层序遍历的模板如下:

Queue<TreeNode>q=new LinkedList<>();
q.add(root);
while(!q.isEmpty()){
  int size=q.size();
  //使用size是可以将这一层的结点进行操作,而不用进入下一层
  while(size-->0){
    TreeNode temp=q.poll();
    if(temp.left!=null){
      //进行相关操作
      q.add(temp.left);
    }
    if(temp.right!=null){
      //进行相关操作
      q.add(temp.right);
    }
  }
}

那么对于这道题来说,则需要对模板进行一些添加。
首先是要在树里面找x和y对应的结点,那么可以增加以下对象:

//x对应的结点
TreeNode xNode=null;
//y对应的结点
TreeNode yNode=null;

然后还要判断两者的父节点是否一样,那么还要再添加父节点:

//x对应的结点的父节点
TreeNode xFather=null;
//y对应结点的父节点
TreeNode yFather=null;

那么再层序遍历的过程中就可以找相应的结点:

if(temp.left!=null){
  q.add(temp.left);
  //找结点
  if(temp.left.val==x){xNode=temp.left;xFather=temp;}
  if(temp.left.val==y){yNode=temp.left;yFather=temp;}
}
if(temp.right!=null){
  q.add(temp.right);
  //找节点
  if(temp.right.val==x){xNode=temp.right;xFather=temp;}
  if(temp.right.val==y){yNode=temp.right;yFather=temp;}
}

接下来对于找到结点的情况进行讨论:
1.如果在遍历过程中两个结点都没找到,那么继续找(什么也不做):

if(xNode==null&&yNode==null){}

2.如果两个结点都找到了,那么判断是否满足要求(父节点不同):

else if(xNode!=null&&yNode!=null){
  return xFather!=yFather;
}

3.如果只找到一个结点,但是这层还没遍历完,那么就继续找;如果这层遍历完了就只找到了一个,说明辈分肯定不同,直接返回false:

else if(size==0)
  return false;

即:

if(xNode==null&&yNode==null){}
else if(xNode!=null&&yNode!=null){
  return xFather==yFather;
}
else if(size==0)
  return false;

那么完整代码如下:

代码
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isCousins(TreeNode root, int x, int y) {
      //这两个值任意一个都不会出现在根节点
      if(root==null||root.val==x||root.val==y)
        return false;
      Queue<TreeNode>q=new LinkedList<>();
      q.add(root);
      //对应x值的结点
      TreeNode xNode=null;
      //对应y值的结点
      TreeNode yNode=null;
      //对应x值的父亲的结点
      TreeNode xFather=null;
      //对应y值的父亲的结点
      TreeNode yFather=null;
      //开始bfs
      while(!q.isEmpty()){
        int size=q.size();
        while(size-->0){
          TreeNode temp=q.poll();
          if(temp.left!=null){
            q.add(temp.left);
            //找节点
            if(temp.left.val==x){xNode=temp.left;xFather=temp;}
            if(temp.left.val==y){yNode=temp.left;yFather=temp;}
          }
          if(temp.right!=null){
            q.add(temp.right);
            //找节点
            if(temp.right.val==x){xNode=temp.right;xFather=temp;}
            if(temp.right.val==y){yNode=temp.right;yFather=temp;}
          }
          //两个节点都没找到,什么也不做
          if(xNode==null&&yNode==null){}
          //两个节点都找到了,那么判断它们是不是堂兄弟节点
          else if(xNode!=null&&yNode!=null){
            //如果父亲结点不相等,说明是堂兄弟结点
            return xFather!=yFather;
          }else if(size==0){
          	return false;
          }//这层遍历完了,但是有一个节点找到了,另外一个没找到
        }
      }
      return false;
    }
}

在这里插入图片描述

(二)、二叉树的层序遍历 II

方法一:广度优先搜索

树的层次遍历可以使用广度优先搜索实现。从根节点开始搜索,每次遍历同一层的全部节点,使用一个列表存储该层的节点值。

如果要求从上到下输出每一层的节点值,做法是很直观的,在遍历完一层节点之后,将存储该层节点值的列表添加到结果列表的尾部。这道题要求从下到上输出每一层的节点值,只要对上述操作稍作修改即可:在遍历完一层节点之后,将存储该层节点值的列表添加到结果列表的头部。

为了降低在结果列表的头部添加一层节点值的列表的时间复杂度,结果列表可以使用链表的结构,在链表头部添加一层节点值的列表的时间复杂度是 O(1)。在 Java 中,由于我们需要返回的 List 是一个接口,这里可以使用链表实现;而 C++ 或 Python 中,我们需要返回一个 vector 或 list,它不方便在头部插入元素(会增加时间开销),所以我们可以先用尾部插入的方法得到从上到下的层次遍历列表,然后再进行反转。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> levelOrder = new LinkedList<List<Integer>>();
        if (root == null) {
            return levelOrder;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            List<Integer> level = new ArrayList<Integer>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);
                TreeNode left = node.left, right = node.right;
                if (left != null) {
                    queue.offer(left);
                }
                if (right != null) {
                    queue.offer(right);
                }
            }
            levelOrder.add(0, level);
        }
        return levelOrder;
    }
}

复杂度分析

时间复杂度:O(n),其中 nnn 是二叉树中的节点个数。每个节点访问一次,结果列表使用链表的结构时,在结果列表头部添加一层节点值的列表的时间复杂度是 O(1),因此总时间复杂度是 O(n)。

空间复杂度:O(n),其中 nnn 是二叉树中的节点个数。空间复杂度取决于队列开销,队列中的节点个数不会超过 nnn。

(三)、二叉树的锯齿形层序遍历

要求我们按层数的奇偶来决定每一层的输出顺序。规定二叉树的根节点为第 0 层,如果当前层数是偶数,从左至右输出当前层的节点值,否则,从右至左输出当前层的节点值。

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> ans = new LinkedList<List<Integer>>();
        if (root == null) {
            return ans
        }

        Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
        nodeQueue.offer(root);
        boolean isOrderLeft = true;

        while (!nodeQueue.isEmpty()) {
            Deque<Integer> levelList = new LinkedList<Integer>();
            int size = nodeQueue.size();
            for (int i = 0; i < size; ++i) {
                TreeNode curNode = nodeQueue.poll();
                if (isOrderLeft) {
                    levelList.offerLast(curNode.val);
                } else {
                    levelList.offerFirst(curNode.val);
                }
                if (curNode.left != null) {
                    nodeQueue.offer(curNode.left);
                }
                if (curNode.right != null) {
                    nodeQueue.offer(curNode.right);
                }
            }
            ans.add(new LinkedList<Integer>(levelList));
            isOrderLeft = !isOrderLeft;
        }
        return ans;
    }
}

复杂度分析

时间复杂度:O(N),其中 NNN 为二叉树的节点数。每个节点会且仅会被遍历一次。

空间复杂度:O(N)。我们需要维护存储节点的队列和存储节点值的双端队列,空间复杂度为 O(N)。

二、数据结构:图中的 BFS

(一)、单词接龙

参考链接:推荐阅读原题题解,本博文仅做本人自己参考总结

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/word-ladder/solution/yan-du-you-xian-bian-li-shuang-xiang-yan-du-you-2/

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/word-ladder/solution/dan-ci-jie-

方法一:广度优先搜索
1. 思路一:建立图

本题要求的是最短转换序列的长度,看到最短首先想到的就是广度优先搜索。想到广度优先搜索自然而然的就能想到图,但是本题并没有直截了当的给出图的模型,因此我们需要把它抽象成图的模型。

我们可以把每个单词都抽象为一个点,如果两个单词可以只改变一个字母进行转换,那么说明他们之间有一条双向边。因此我们只需要把满足转换条件的点相连,就形成了一张图。

基于该图,我们以 beginWord 为图的起点,以 endWord 为终点进行广度优先搜索,寻找 beginWord 到 endWord 的最短路径。

在这里插入图片描述

基于上面的思路我们考虑如何编程实现。

首先为了方便表示,我们先给每一个单词标号,即给每个单词分配一个 id。创建一个由单词 word 到 id 对应的映射 wordId,并将 beginWord 与 wordList 中所有的单词都加入这个映射中。之后我们检查 endWord 是否在该映射内,若不存在,则输入无解。我们可以使用哈希表实现上面的映射关系。

然后我们需要建图,依据朴素的思路,我们可以枚举每一对单词的组合,判断它们是否恰好相差一个字符,以判断这两个单词对应的节点是否能够相连。但是这样效率太低,我们可以优化建图

具体地,我们可以创建虚拟节点。对于单词 hit,我们创建三个虚拟节点 it、ht、hi*,并让 hit 向这三个虚拟节点分别连一条边即可。如果一个单词能够转化为 hit,那么该单词必然会连接到这三个虚拟节点之一。对于每一个单词,我们枚举它连接到的虚拟节点,把该单词对应的 id 与这些虚拟节点对应的 id 相连即可。

最后我们将起点加入队列开始广度优先搜索,当搜索到终点时,我们就找到了最短路径的长度。注意因为添加了虚拟节点,所以我们得到的距离为实际最短路径长度的两倍。同时我们并未计算起点对答案的贡献,所以我们应当返回距离的一半再加一的结果

代码一
class Solution {
    Map<String, Integer> wordId = new HashMap<String, Integer>();
    List<List<Integer>> edge = new ArrayList<List<Integer>>();
    int nodeNum = 0;

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        for (String word : wordList) {
            addEdge(word);
        }
        addEdge(beginWord);
        if (!wordId.containsKey(endWord)) {
            return 0;
        }
        int[] dis = new int[nodeNum];
        Arrays.fill(dis, Integer.MAX_VALUE);
        int beginId = wordId.get(beginWord), endId = wordId.get(endWord);
        dis[beginId] = 0;

        Queue<Integer> que = new LinkedList<Integer>();
        que.offer(beginId);
        while (!que.isEmpty()) {
            int x = que.poll();
            if (x == endId) {
                return dis[endId] / 2 + 1;
            }
            for (int it : edge.get(x)) {
                if (dis[it] == Integer.MAX_VALUE) {
                    dis[it] = dis[x] + 1;
                    que.offer(it);
                }
            }
        }
        return 0;
    }

    public void addEdge(String word) {
        addWord(word);
        int id1 = wordId.get(word);
        char[] array = word.toCharArray();
        int length = array.length;
        for (int i = 0; i < length; ++i) {
            char tmp = array[i];
            array[i] = '*';
            String newWord = new String(array);
            addWord(newWord);
            int id2 = wordId.get(newWord);
            edge.get(id1).add(id2);
            edge.get(id2).add(id1);
            array[i] = tmp;
        }
    }

    public void addWord(String word) {
        if (!wordId.containsKey(word)) {
            wordId.put(word, nodeNum++);
            edge.add(new ArrayList<Integer>());
        }
    }
}
复杂度分析

时间复杂度: O ( N × C 2 ) O(N \times C^2) O(N×C2)。其中 NNN 为 wordList 的长度, C C C 为列表中单词的长度。

建图过程中,对于每一个单词,我们需要枚举它连接到的所有虚拟节点,时间复杂度为 O©,将这些单词加入到哈希表中,时间复杂度为 O ( N × C ) O(N \times C) O(N×C),因此总时间复杂度为 O ( N × C ) O(N \times C) O(N×C)

广度优先搜索的时间复杂度最坏情况下是 O ( N × C ) O(N \times C) O(N×C)。每一个单词需要拓展出 O ( C ) O(C) O(C)个虚拟节点,因此节点数 O ( N × C ) O(N \times C) O(N×C)

空间复杂度: O ( N × C 2 ) O(N \times C^2) O(N×C2)。其中 N 为 wordList 的长度, C C C 为列表中单词的长度。哈希表中包含 O ( N × C ) O(N \times C) O(N×C)个节点,每个节点占用空间 O ( C ) O(C) O(C),因此总的空间复杂度为 O ( N × C 2 ) O(N \times C^2) O(N×C2)

2. 思路二:使用朴素思想,不建立图

如果一开始就构建图,每一个单词都需要和除它以外的另外的单词进行比较,复杂度是 O(NwordLen)O(N \rm{wordLen})O(NwordLen),这里 NNN 是单词列表的长度;
为此,我们在遍历一开始,把所有的单词列表放进一个哈希表中,然后在遍历的时候构建图,每一次得到在单词列表里可以转换的单词,复杂度是 O(26×wordLen)O(26 \times \rm{wordLen})O(26×wordLen),借助哈希表,找到邻居与 NNN 无关;
使用 BFS 进行遍历,需要的辅助数据结构是:

队列;
visited 集合。说明:可以直接在 wordSet (由 wordList 放进集合中得到)里做删除。但更好的做法是新开一个哈希表,遍历过的字符串放进哈希表里。这种做法具有普遍意义。绝大多数在线测评系统和应用场景都不会在意空间开销。
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;

public class Solution {

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        // 第 1 步:先将 wordList 放到哈希表里,便于判断某个单词是否在 wordList 里
        Set<String> wordSet = new HashSet<>(wordList);
        if (wordSet.size() == 0 || !wordSet.contains(endWord)) {
            return 0;
        }
        wordSet.remove(beginWord);
        
        // 第 2 步:图的广度优先遍历,必须使用队列和表示是否访问过的 visited 哈希表
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);
        Set<String> visited = new HashSet<>();
        visited.add(beginWord);
        
        // 第 3 步:开始广度优先遍历,包含起点,因此初始化的时候步数为 1
        int step = 1;
        while (!queue.isEmpty()) {
            int currentSize = queue.size();
            for (int i = 0; i < currentSize; i++) {
                // 依次遍历当前队列中的单词
                String currentWord = queue.poll();
                // 如果 currentWord 能够修改 1 个字符与 endWord 相同,则返回 step + 1
                if (changeWordEveryOneLetter(currentWord, endWord, queue, visited, wordSet)) {
                    return step + 1;
                }
            }
            step++;
        }
        return 0;
    }

    /**
     * 尝试对 currentWord 修改每一个字符,看看是不是能与 endWord 匹配
     *
     * @param currentWord
     * @param endWord
     * @param queue
     * @param visited
     * @param wordSet
     * @return
     */
    private boolean changeWordEveryOneLetter(String currentWord, String endWord,
                                             Queue<String> queue, Set<String> visited, Set<String> wordSet) {
        char[] charArray = currentWord.toCharArray();
        for (int i = 0; i < endWord.length(); i++) {
            // 先保存,然后恢复
            char originChar = charArray[i];
            for (char k = 'a'; k <= 'z'; k++) {
                if (k == originChar) {
                    continue;
                }
                charArray[i] = k;
                String nextWord = String.valueOf(charArray);
                if (wordSet.contains(nextWord)) {
                    if (nextWord.equals(endWord)) {
                        return true;
                    }
                    if (!visited.contains(nextWord)) {
                        queue.add(nextWord);
                        // 注意:添加到队列以后,必须马上标记为已经访问
                        visited.add(nextWord);
                    }
                }
            }
            // 恢复
            charArray[i] = originChar;
        }
        return false;
    }
}
方法二:双向广度优先搜索
1.思路一

根据给定字典构造的图可能会很大,而广度优先搜索的搜索空间大小依赖于每层节点的分支数量。假如每个节点的分支数量相同,搜索空间会随着层数的增长指数级的增加。考虑一个简单的二叉树,每一层都是满二叉树的扩展,节点的数量会以 2 为底数呈指数增长。

如果使用两个同时进行的广搜可以有效地减少搜索空间。一边从 beginWord 开始,另一边从 endWord 开始。我们每次从两边各扩展一层节点,当发现某一时刻两边都访问过同一顶点时就停止搜索。这就是双向广度优先搜索,它可以可观地减少搜索空间大小,从而提高代码运行效率。

在这里插入图片描述

代码一
class Solution {
    Map<String, Integer> wordId = new HashMap<String, Integer>();
    List<List<Integer>> edge = new ArrayList<List<Integer>>();
    int nodeNum = 0;

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        for (String word : wordList) {
            addEdge(word);
        }
        addEdge(beginWord);
        if (!wordId.containsKey(endWord)) {
            return 0;
        }

        int[] disBegin = new int[nodeNum];
        Arrays.fill(disBegin, Integer.MAX_VALUE);
        int beginId = wordId.get(beginWord);
        disBegin[beginId] = 0;
        Queue<Integer> queBegin = new LinkedList<Integer>();
        queBegin.offer(beginId);
        
        int[] disEnd = new int[nodeNum];
        Arrays.fill(disEnd, Integer.MAX_VALUE);
        int endId = wordId.get(endWord);
        disEnd[endId] = 0;
        Queue<Integer> queEnd = new LinkedList<Integer>();
        queEnd.offer(endId);

        while (!queBegin.isEmpty() && !queEnd.isEmpty()) {
            int queBeginSize = queBegin.size();
            for (int i = 0; i < queBeginSize; ++i) {
                int nodeBegin = queBegin.poll();
                if (disEnd[nodeBegin] != Integer.MAX_VALUE) {
                    return (disBegin[nodeBegin] + disEnd[nodeBegin]) / 2 + 1;
                }
                for (int it : edge.get(nodeBegin)) {
                    if (disBegin[it] == Integer.MAX_VALUE) {
                        disBegin[it] = disBegin[nodeBegin] + 1;
                        queBegin.offer(it);
                    }
                }
            }

            int queEndSize = queEnd.size();
            for (int i = 0; i < queEndSize; ++i) {
                int nodeEnd = queEnd.poll();
                if (disBegin[nodeEnd] != Integer.MAX_VALUE) {
                    return (disBegin[nodeEnd] + disEnd[nodeEnd]) / 2 + 1;
                }
                for (int it : edge.get(nodeEnd)) {
                    if (disEnd[it] == Integer.MAX_VALUE) {
                        disEnd[it] = disEnd[nodeEnd] + 1;
                        queEnd.offer(it);
                    }
                }
            }
        }
        return 0;
    }

    public void addEdge(String word) {
        addWord(word);
        int id1 = wordId.get(word);
        char[] array = word.toCharArray();
        int length = array.length;
        for (int i = 0; i < length; ++i) {
            char tmp = array[i];
            array[i] = '*';
            String newWord = new String(array);
            addWord(newWord);
            int id2 = wordId.get(newWord);
            edge.get(id1).add(id2);
            edge.get(id2).add(id1);
            array[i] = tmp;
        }
    }

    public void addWord(String word) {
        if (!wordId.containsKey(word)) {
            wordId.put(word, nodeNum++);
            edge.add(new ArrayList<Integer>());
        }
    }
}
复杂度分析
时间复杂度:O(N×C2)O(N \times C^2)O(N×C2)。其中 N 为 wordList 的长度,C 为列表中单词的长度。

    建图过程中,对于每一个单词,我们需要枚举它连接到的所有虚拟节点,时间复杂度为 O(C),将这些单词加入到哈希表中,时间复杂度为 O(N×C)O(N \times C)O(N×C),因此总时间复杂度为 O(N×C)O(N \times C)O(N×C)。

    双向广度优先搜索的时间复杂度最坏情况下是 O(N×C)O(N \times C)O(N×C)。每一个单词需要拓展出 O(C)个虚拟节点,因此节点数 O(N×C)O(N \times C)O(N×C)。

空间复杂度:O(N×C2)O(N \times C^2)O(N×C2)。其中 NNN 为 wordList 的长度,CCC 为列表中单词的长度。哈希表中包含 O(N×C)O(N \times C)O(N×C) 个节点,每个节点占用空间 O(C)O(C)O(C),因此总的空间复杂度为 O(N×C2)O(N \times C^2)O(N×C2)。

https://leetcode-cn.com/problems/word-ladder/solution/dan-ci-jie-long-by-leetcode-solution/

2.思路二

已知目标顶点的情况下,可以分别从起点和目标顶点(终点)执行广度优先遍历,直到遍历的部分有交集。这种方式搜索的单词数量会更小一些;
更合理的做法是,每次从单词数量小的集合开始扩散;
这里 beginVisited 和 endVisited 交替使用,等价于单向 BFS 里使用队列,每次扩散都要加到总的 visited 里。

代码二
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Solution {

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        // 第 1 步:先将 wordList 放到哈希表里,便于判断某个单词是否在 wordList 里
        Set<String> wordSet = new HashSet<>(wordList);
        if (wordSet.size() == 0 || !wordSet.contains(endWord)) {
            return 0;
        }

        // 第 2 步:已经访问过的 word 添加到 visited 哈希表里
        Set<String> visited = new HashSet<>();
        // 分别用左边和右边扩散的哈希表代替单向 BFS 里的队列,它们在双向 BFS 的过程中交替使用
        Set<String> beginVisited = new HashSet<>();
        beginVisited.add(beginWord);
        Set<String> endVisited = new HashSet<>();
        endVisited.add(endWord);

        // 第 3 步:执行双向 BFS,左右交替扩散的步数之和为所求
        int step = 1;
        while (!beginVisited.isEmpty() && !endVisited.isEmpty()) {
            // 优先选择小的哈希表进行扩散,考虑到的情况更少
            if (beginVisited.size() > endVisited.size()) {
                Set<String> temp = beginVisited;
                beginVisited = endVisited;
                endVisited = temp;
            }

            // 逻辑到这里,保证 beginVisited 是相对较小的集合,nextLevelVisited 在扩散完成以后,会成为新的 beginVisited
            Set<String> nextLevelVisited = new HashSet<>();
            for (String word : beginVisited) {
                if (changeWordEveryOneLetter(word, endVisited, visited, wordSet, nextLevelVisited)) {
                    return step + 1;
                }
            }

            // 原来的 beginVisited 废弃,从 nextLevelVisited 开始新的双向 BFS
            beginVisited = nextLevelVisited;
            step++;
        }
        return 0;
    }


    /**
     * 尝试对 word 修改每一个字符,看看是不是能落在 endVisited 中,扩展得到的新的 word 添加到 nextLevelVisited 里
     *
     * @param word
     * @param endVisited
     * @param visited
     * @param wordSet
     * @param nextLevelVisited
     * @return
     */
    private boolean changeWordEveryOneLetter(String word, Set<String> endVisited,
                                             Set<String> visited,
                                             Set<String> wordSet,
                                             Set<String> nextLevelVisited) {
        char[] charArray = word.toCharArray();
        for (int i = 0; i < word.length(); i++) {
            char originChar = charArray[i];
            for (char c = 'a'; c <= 'z'; c++) {
                if (originChar == c) {
                    continue;
                }
                charArray[i] = c;
                String nextWord = String.valueOf(charArray);
                if (wordSet.contains(nextWord)) {
                    if (endVisited.contains(nextWord)) {
                        return true;
                    }
                    if (!visited.contains(nextWord)) {
                        nextLevelVisited.add(nextWord);
                        visited.add(nextWord);
                    }
                }
            }
            // 恢复,下次再用
            charArray[i] = originChar;
        }
        return false;
    }
}

三、数据结构:网格中的 BFS

(一)、岛屿数量

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

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

示例 2:

输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 ‘0’ 或 ‘1’
试题链接

https://leetcode-cn.com/problems/number-of-islands/

解题思路

我们可以将二维网格看成一个无向图,竖直或水平相邻的 1 之间有边相连。

为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1 ,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0 。

BFS 解法:

class Solution {
    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0){
            return 0;
        }

        int rowNum = grid.length;
        int colNum = grid[0].length;
        int num_islands = 0;

        for (int r = 0; r < rowNum; r++){
            for (int c = 0; c < colNum; ++c){
                if (grid[r][c] == '1'){
                    num_islands = num_islands + 1;
                    grid[r][c] = '0';
                    Queue<Integer> queue = new LinkedList();
                    //在网格类中使用整数排序代替元素位置;row = tem / colNum;  col = tem % colNum;
                    queue.add(r * colNum + c);
                    while(!queue.isEmpty()){
                        int tem = queue.poll();
                        int row = tem / colNum;
                        int col = tem % colNum;
                        if (row - 1 >= 0 && grid[row-1][col] == '1'){
                            queue.offer((row-1)*colNum+col);
                            grid[row-1][col]  = '0';
                        }
                        if (col + 1 < colNum && grid[row][col+1] == '1'){
                            queue.offer(row * colNum + col + 1);
                            grid[row][col+1] = '0';
                        }
                        if (row + 1 < rowNum && grid[row+1][col] == '1'){
                            queue.offer((row+1) * colNum + col);
                            grid[row+1][col] = '0';
                        }
                        if (col - 1 >= 0 && grid[row][col-1]=='1'){
                            queue.offer(row * colNum + col -1);
                            grid[row][col - 1] = '0';
                        }
                    
                    }
                }
            }
        }
        return num_islands;
    }
}
复杂度分析
时间复杂度:O(MN),其中 M 和 N 分别为行数和列数。

空间复杂度:O(min⁡(M,N)),在最坏情况下,整个网格均为陆地,队列的大小可以达到 min⁡(M,N)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何为xl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值