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

  • 一、数据结构:二叉树中的 BFS
    • (一)、二叉树的堂兄弟节点
      • 试题链接
      • 解题思路
      • 代码
    • (二)、二叉树的层序遍历 II
    • (三)、二叉树的锯齿形层序遍历
  • 二、数据结构:图中的 BFS
    • (一)、单词接龙
      • 参考链接:推荐阅读原题题解,本博文仅做本人自己参考总结
      • 方法一:广度优先搜索
        • 1. 思路一:建立图
        • 代码一
        • 复杂度分析
        • 2. 思路二:使用朴素思想,不建立图
      • 方法二:双向广度优先搜索
        • 1.思路一
        • 代码一
        • 复杂度分析
        • 2.思路二
        • 代码二
  • 三、数据结构:网格中的 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;//开始bfswhile(!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×C2)O(N \times C^2)O(N×C2)。其中 NNN 为 wordList 的长度,CCC 为列表中单词的长度。

建图过程中,对于每一个单词,我们需要枚举它连接到的所有虚拟节点,时间复杂度为 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×C2)O(N \times C^2)O(N×C2)。其中 N 为 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)。

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 步:开始广度优先遍历,包含起点,因此初始化的时候步数为 1int step = 1;while (!queue.isEmpty()) {int currentSize = queue.size();for (int i = 0; i < currentSize; i++) {// 依次遍历当前队列中的单词String currentWord = queue.poll();// 如果 currentWord 能够修改 1 个字符与 endWord 相同,则返回 step + 1if (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 在扩散完成以后,会成为新的 beginVisitedSet<String> nextLevelVisited = new HashSet<>();for (String word : beginVisited) {if (changeWordEveryOneLetter(word, endVisited, visited, wordSet, nextLevelVisited)) {return step + 1;}}// 原来的 beginVisited 废弃,从 nextLevelVisited 开始新的双向 BFSbeginVisited = 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)。

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

  1. 【蓝桥杯】历届试题 青蛙跳杯子(广度优先搜索bfs)(C++)

    [蓝桥杯]历届试题 青蛙跳杯子 问题描述 思路分析 代码实现 问题描述 题目链接:青蛙跳杯子 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 X星球的流行宠物是青蛙,一般有两种颜色: ...

  2. 广度优先搜索BFS进阶(一):多源BFS、优先队列BFS、双端队列BFS

    一.多源BFS 在上一篇博客:广度优先搜索BFS基础中,我们接触到的BFS均是单起点(单源)的,但是对于某一些问题,其有多个起点,此类问题我们称为多源BFS问题.先思考下面一道例题: 1.腐烂的橘子 ...

  3. 一文搞定深度优先搜索(DFS)与广度优先搜索(BFS)【含完整源码】

    写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站.博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事 ...

  4. 广度优先搜索(BFS)——抓住那头牛(POJ 4001)

    本文将以(POJ 4001)抓住那头牛 为例,讲解经典算法广度优先搜索(BFS)的STL写法 在实际写算法中,怎么能不使用更快.更方便.更准确.更高效的C++ STL模板呢 相信很多人都了解过广度优先 ...

  5. 算法简介:层层递进----广度优先搜索(BFS)

    算法简介:层层递进----广度优先搜索(BFS) 算法简介 算法简介 BFS算法思想: 首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点,然后对每个相邻的顶点,再访问它们相邻的未被访问过的 ...

  6. 算法复习|广度优先搜索BFS

    广度优先搜索BFS 文章目录 广度优先搜索BFS HDU-1253 胜利大逃亡 HDU-1241 Oil Deposits 算法思想 从初始状态S开始,利用一定的规则,生成所有下一层的状态,依次入队 ...

  7. LeetCode 1306. 跳跃游戏 III(广度优先搜索BFS)

    1. 题目 这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处. 当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]. 请你判断自己是否能够 ...

  8. 【蓝桥杯】2015决赛A组 5 穿越雷区(深度优先搜索dfs、广度优先搜索bfs)

    历届试题 穿越雷区 问题描述 X星的坦克战车很奇怪,它必须交替地穿越正能量辐射区和负能量辐射区才能保持正常运转,否则将报废. 某坦克需要从A区到B区去(A,B区本身是安全区,没有正能量或负能量特征), ...

  9. matlab bfs函数,matlab练习程序(广度优先搜索BFS、深度优先搜索DFS)

    如此经典的算法竟一直没有单独的实现过,真是遗憾啊. 广度优先搜索在过去实现的二值图像连通区域标记和prim最小生成树算法时已经无意识的用到了,深度优先搜索倒是没用过. 这次单独的将两个算法实现出来,因 ...

  10. 广度优先搜索 BFS算法

    广度优先搜索算法(Breadth-First-Search,BFS),又称作宽度优先搜索.BFS算法是从根节点开始,沿着树的宽度遍历树的节点.如果所有节点均被访问,则算法中止. 算法思想 1.首先将根 ...

最新文章

  1. linux 集群配置ssh无密码访问
  2. mysql-connector-net-6.7.4.msi,在ActiveReports中使用MySQL数据库
  3. JavaWeb(十一)——登录注册小案例
  4. [转]通过创建一个位图的XY Chart来学习Android绘图类Rect,Paint,Bitmap,Canvas(附源码)...
  5. 8086汇编4位bcd码_238期中4头3尾,排列五第19239期爱我彩规
  6. 在.NET Core中使用DispatchProxy“实现”非公开的接口
  7. Android开发性能优化大总结
  8. 微信 vue html缓存,解决微信浏览器缓存站点入口文件(IIS部署Vue项目)_唇印_前端开发者...
  9. 古籍排版 -古籍重制 - 杨式梨花枪 - 古籍制作 - 古籍制作工具 - 古籍制作软件 - 古籍修复工具 - 古书制作 - 古书制作软件 - 古书修复工具,
  10. 怎么查看电脑系统的初始安装日期
  11. 菜鸟入门:Java语言学习
  12. PDF如何在线压缩?PDF压缩到最小的方法
  13. mysql将时间戳转换为年月日格式进行查询
  14. Explicit 关键字
  15. (ICLR2019)论文阅读-使用深度增强学习框架的基于场景先验知识的视觉语义导航
  16. 微信里的apk链接打不开,怎么让微信的扫一扫扫描二维码后安装apk?
  17. 汇编实现通过输入被加数与加数,实现加法
  18. 通过命令(cli)配置华为AP的热点以及账号密码
  19. 向量几何在游戏编程中的使用2
  20. 数学建模--运输问题

热门文章

  1. “用户体验”热浪扑面来袭,空调冰西瓜小板凳准备好了吗?
  2. BUUCTF--[网鼎杯 2020 半决赛]AliceWebsite
  3. 猿创征文|OpenCV编程——计算机视觉的登堂入室
  4. 驻场是指进驻甲方的办公场地吗?
  5. 【主动选择,远离忧患】云和恩墨大讲堂电子期刊第十一期
  6. SpringBoot启动流程类图版
  7. Linux--RAID磁盘阵列学习使用(详细学习)
  8. dp主机_DP接口和HDMI接口分别怎么用?哪个更好?两者的区别?
  9. 计算机领慧讲堂背景图片,在考生文件夹下打开文档WORD.DOCX。
  10. linux gcc 6源码安装教程,CentOS 6.5下源码安装GCC-4.8.2安装笔记整理