六、BFS广度优先搜索
总结
- 遍历
- 最短路径
- 双向BFS
最重要的是层的概念
广度优先搜索有个基础模板:
1、root为空就直接返回;
2、queue队列q,先放入root;
3、while的循环条件是队列q不为空—取当前队列的大小(也就是这一层元素的个数)—for循环这一层-取出顶层元素(对此元素的左右进行添加);
遍历意味着:我在此时能过拿到这个元素,并对这个元素进行操作。
总:
- while(遍历所有)
- for(遍历一层)
- q.poll()拿出一个
- 加值-----很关键:加的是他下一次所有可能情况----这个层的所有元素的可能情况就构成了下一层
public void levelOrder(TreeNode root) { // 1、为空返回 if(root == null) return; // 2、Queue队列 Queue<TreeNode> q = new LinkedList<>(); q.offer(root); // 3、①进行while循环 while(!q.isEmpty()){ // ②这一层的元素个数 int size = q.size(); // 对这一层元素进行遍历 for(int i = 0; i < size; i++){ // ③取值 TreeNode cur = q.poll(); // 对值得操作 sout(cur.val); // ④加可能情况 q.offer(cur下一次的可能情况); } res.add(level); } return res; }
广度搜索有个明确得目标:路径最短(上述基础上)-----把这个值拿出来符合某个情况
基础条件:遍历每一层元素的时候,到这一层之前所走的路步数都是一样的;
结束条件:当某一层的每个元素符合了结束条件,由于该层的元素往上路的步数都一直,到它就变了,所以它是最短,即return
public int minDepth(TreeNode root) { if(root == null) return 0; Queue<TreeNode> q = new LinkedList<>(); q.offer(root); int step = 0; while(!q.isEmpty()){ int size = q.size(); // ①到此层所走的步数 step++; for(int i = 0; i < size; i++){ TreeNode cur = q.poll(); // ②结束条件 if(满足结束条件) return step; // ③所有可能情况 q.offer(cur下一次的可能情况); } } return step; }
双向BFS
1、条件是最短路径的目标明确
2、双向BFS的是能实现的依据:
由于随着每一层的元素越来越多,那么下一层涵盖的元素会因为上一次元素的增多而增多,因为下一层来源于上一层下一次可能的所有情况么;
因此,由于目标值明确,以目标值出发,就像以根节点出发一样:起始都是1个点;结束条件就在两者交汇处,即最短距离。
3、关键点在于:
①起始值与目标值二者队列的存储—用俩个队列进行存储
②下一层的存储:Queue temp
③结束条件:当前元素包含在q2中
④双向的交换
public int ladderLength(String beginWord, String endWord) { // ①起始值与目标值二者队列的存储 Queue<String> q1 = new LinkedList<String>(); Queue<String> q2 = new LinkedList<String>(); q1.offer(beginWord); q2.offer(endWord); int step = 0; while(!q1.isEmpty()){ // ②q1或q2的暂时存储 Queue<String> temp = new LinkedList<String>(); int size = q1.size(); step++; for(int i = 0; i < size; i++){ TreeNode cur = q.poll(); // ③结束条件 if(cur包含在q2中) return step; temp.offer(cur下一次的可能情况); } //④、交换 q1 = q2; q2 = temp; } return 0; }
1、遍历
1.1 子对象是叶结点
题目地址(102. 二叉树的层序遍历)
https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
题目描述
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 示例 1: 输入:root = [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]] 示例 2: 输入:root = [1] 输出:[[1]] 示例 3: 输入:root = [] 输出:[] 提示: 树中节点数目在范围 [0, 2000] 内 -1000 <= Node.val <= 1000
代码
class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res = new LinkedList<>(); // 1、为空直接返回 if(root == null) return res; // 2、加入初始值 Queue<TreeNode> q = new LinkedList<>(); q.offer(root); // 3、①所有值的遍历 while(!q.isEmpty()){ // ②当前层 List<Integer> level = new LinkedList<>(); int size = q.size(); for(int i = 0; i < size; i++){ // ④取出这一层得一个值 TreeNode cur = q.poll(); // 对这个值进行操作 level.add(cur.val); // ⑤加值 if(cur.left!=null) q.offer(cur.left); if(cur.right!=null) q.offer(cur.right); } res.add(level); } return res; } }
1.2 子对象是图的邻居
题目地址(207. 课程表)
https://leetcode-cn.com/problems/course-schedule/
题目描述
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。 请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。 示例 1: 输入:numCourses = 2, prerequisites = [[1,0]] 输出:true 解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。 示例 2: 输入:numCourses = 2, prerequisites = [[1,0],[0,1]] 输出:false 解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。 提示: 1 <= numCourses <= 105 0 <= prerequisites.length <= 5000 prerequisites[i].length == 2 0 <= ai, bi < numCourses prerequisites[i] 中的所有课程对 互不相同
代码
class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { // 下一次的可能性 int[] indegree = new int[numCourses]; List<Integer>[] graph = new LinkedList[numCourses]; for(int i = 0;i < graph.length;i++){ graph[i] = new LinkedList<Integer>(); } for(int[] edge:prerequisites){ int from = edge[1];int to = edge[0]; indegree[to]++; graph[from].add(to); } // 1、添加初始值 Queue<Integer> q = new LinkedList<>(); for(int i = 0; i < indegree.length; i++){ if(indegree[i] == 0) q.offer(i); } // 2、所有值的遍历,没有层的概念,记录元素的个数 int count = 0; while(!q.isEmpty()){ int cur = q.poll(); count++; // 3、下个的可能性,与是否添加进去 for(int neighbor : graph[cur]){ indegree[neighbor]--; if(indegree[neighbor] == 0){ q.offer(neighbor); } } } // 3、对比是否与课程数相同 return count == numCourses; } }
1.3 子对象是邻接点
题目地址(542. 01 矩阵)
https://leetcode-cn.com/problems/01-matrix/
题目描述
给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。 两个相邻元素间的距离为 1 。 示例 1: 输入:mat = [[0,0,0],[0,1,0],[0,0,0]] 输出:[[0,0,0],[0,1,0],[0,0,0]] 提示: m == mat.length n == mat[i].length 1 <= m, n <= 104 1 <= m * n <= 104 mat[i][j] is either 0 or 1. mat 中至少有一个 0
代码
class Solution { public int[][] updateMatrix(int[][] mat) { int m = mat.length; int n = mat[0].length; Queue<int[]> q = new LinkedList<>(); HashSet<Integer> set = new HashSet<>(); // 1、加入初始值,mat[i][j]=0,最短距离就是它本身,依次往后阔大层数 for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ if(mat[i][j] == 0){ q.offer(new int[]{i,j}); set.add(i*n+j); } } } // 2、每一层的循 int step = 0; while(!q.isEmpty()){ int size = q.size(); for(int i = 0;i < size;i++){ int[] edge = q.poll(); int x = edge[0];int y = edge[1]; // 3、把每一层的值给每一个数 mat[x][y] = step; // 4、下一层可能的数的坐标 int[][] neighbors = new int[][]{{1,0},{0,1},{-1,0},{0,-1}}; for(int[] neighbor:neighbors){ int x1 = x + neighbor[0]; int y1 = y + neighbor[1]; if(x1 < 0||x1 >= mat.length||y1 < 0||y1 >= mat[0].length) continue; if(set.contains(x1*n+y1)) continue;// 防止多加 q.offer(new int[] {x1,y1}); set.add(x1*n+y1); } } step++; } return mat; } }
2、最短路径
2.1 最小高度-子对象叶结点
题目地址(111. 二叉树的最小深度)
https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/
题目描述
给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明:叶子节点是指没有子节点的节点。 示例 1: 输入:root = [3,9,20,null,null,15,7] 输出:2 示例 2: 输入:root = [2,null,3,null,4,null,5,null,6] 输出:5 提示: 树中节点数的范围在 [0, 105] 内 -1000 <= Node.val <= 1000
代码
class Solution { public int minDepth(TreeNode root) { if(root == null) return 0; Queue<TreeNode> q = new LinkedList<>(); q.offer(root); int step = 0; while(!q.isEmpty()){ int size = q.size(); // ①到此层所走的步数 step++; for(int i = 0; i < size; i++){ TreeNode cur = q.poll(); // ②结束条件 if(cur.left == null&&cur.right == null) return step; // ③所有可能情况 if(cur.left!=null) q.offer(cur.left); if(cur.right!=null) q.offer(cur.right); } } return step; } }
3、双向BFS
3.1 额外条件是不重复且在规定字典中
题目地址(127. 单词接龙)
https://leetcode-cn.com/problems/word-ladder/
题目描述
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk: 每一对相邻的单词只差一个字母。 对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。 sk == endWord 给你两个单词 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 中的所有字符串 互不相同
代码
class Solution { HashSet<String> map; HashSet<String> visit; public int ladderLength(String beginWord, String endWord, List<String> wordList) { // 为取下一次肯能行且不重复做准备 map = new HashSet<>(); visit = new HashSet<>(); for(String st:wordList){ map.add(st); } if(!map.contains(endWord)) return 0; // ①起始值与目标值的储存: // 用set是因为,方便取值,此时每一次的取值存放在temp中, // 不会引发新增值而导致q原本的变化 HashSet<String> q1 = new HashSet<String>(); HashSet<String> q2 = new HashSet<String>(); q1.add(beginWord); q2.add(endWord); int step = 0; while(!q1.isEmpty()&&!q2.isEmpty()){ // ②q1或q2的存储 HashSet temp = new HashSet<>(); step++; for(String st:q1){ // 本题其他条件,不允许重返 if(visit.contains(st)) continue; visit.add(st); // ③当q2中包括这个值,说明找到了 // 特别说明的是:当以目标向上遍历时, // q2存的是上一次向下遍历时下一层的可能情况 if(q2.contains(st)) return step; // 下一层可能情况 List<String> neighbors = findNeighbors(st); for(String st1:neighbors){ if(!visit.contains(st1)){ temp.add(st1); } } } // ④双向交换,q1已经用完了 q1 = q2; q2 = temp; } return 0; } private List<String> findNeighbors(String cur){ List<String> res = new LinkedList<>(); for(int i = 0;i < cur.length();i++){ for(char ch = 'a';ch<='z';ch++){ char[] chs = cur.toCharArray(); chs[i] = ch; String temp = new String(chs); if(map.contains(temp)&&!visit.contains(temp)) res.add(temp); } } return res; } }