lcBFS1
1 BFS
最关键的是:
- bfs本身的特性,一个是按照层数往下遍历,一个是可以决定是否遍历完当前层再往下遍历
- bfs的队列初始化即为bfs的搜索起点,然后注意状态问题,当每个节点带有状态以后,bfs的visited变量就不仅仅是node的编号,应该是node的编号 + “当前状态”,具体参考(0864,0847)
- 注意:遍历当前层,需要把size记录成临时变量,因为curLevel会加入新元素
- 注意:入队后就需要置为visited,否则可能重复入队
bfs的遍历本质上可以看成当前点,到所有后继节点的可能跳转的状态。(0733例题)
bfs的核心代码:
1 | int bfsCheck(vector<vector<int>>& oriBoard) { |
2 例题
0733slidingPuzzle 滑动到123450谜题
1 题目
https://leetcode.cn/problems/sliding-puzzle/
2 解题思路
- 1 解题思路:
- 1.1 为何选用bfs,因为要获得最终的移动步数,那么bfs的层数就是最终的答案
- 1.2 节点就是当前的棋盘,后继节点为移动后改变的棋盘
- 1.3 使用位运算加速
1 | class Solution { |
0675golfCutTree 高尔夫砍树
1 题目
https://leetcode.cn/problems/cut-off-trees-for-golf-event/
2 解题思路
- 1 解题思路:
- 1.1 使用优先队列将所有树从小到高排序,存为trees
- 1.2 每次从trees中取出一个树,对其进行bfs直到找到trees的下一个目标
- 2 bfs思路:
- 2.1 queue初始化为起点
- 2.2 对于queue中所有元素,加入当前queue的所有后继,加入以后就把元素置为visited
- 2.3 进入下一个level
- 3 为什么加入的过程中就要吧元素置为vis?因为:假设1层有a,b, 2层有c,c为ab的邻居,那么遍历第1层的时候,对于ab的后继,到a,加入c,若不置为vis,则b会再加入一遍,导致bfs很慢!
1 | class Solution { |
0847allTravelShortestPath 访问所有节点最短路径
1 题目
https://leetcode.cn/problems/shortest-path-visiting-all-nodes/
2 解题思路
- 1 解题思路:
- 1.1 使用三元组 <u, mask, dist>表示,当前节点u的mask的搜索情况对应的搜索距离dist,调用bfs即可,但是对于下一个v,我们需要检测: v节点的1 << v | mask这种访问情况是否被访问过
- 1.2 如何考虑这个方法?
- 1.2.1 初始化的时候所有节点都加入队里(认为可以从每个节点出发)
- 1.2.2 bfs的具体过程中,退出条件就是 队首的mask是否标记了所有节点都被访问
- 1.3 这个方法为什么可以?
- 一句话:这个方法:利用bfs,按照路径长度从小到大遍历了所有的可能路径(队列初始化有所有的顶点就是这个意思),比如初始化,就是说将长度为0的所有可能路径遍历完成,然后下一层bfs会将所有长度为1的可能路径遍历完成,这个路径的记录方式为mask以及mask对应的终点u,那么由于路径长度是从小到大去遍历的,那么必然保证最终答案是最短路径,
- 假设用dfs做,那么dfs要考虑所有路径可能,然后去比较,那么会出现枚举的情况,然鹅bfs不会,因为省去所有长度比最短路径大的路径
- 2 总结一下:最关键的有两点
- 2.1 利用bfs能够将遍历路径的长度从小到大遍历的特性
- 2.2 使用<到达点,已经遍历的点,目前长度>来记录所有的遍历情况这个trick很聪明
1 | // 这里看一个具体 |
1 | class Solution { |
0864getAllKeys 获取所有钥匙的最短路径
1 题目
https://leetcode.cn/problems/shortest-path-to-get-all-keys/submissions/
2 解题思路
- 1 解题思路:
- 1.1 首先考虑使用bfs,因为从起点到终点,bfs的遍历深度就等于最短路径,那么有一个问题对于:[“@a.”,”bAB”]这样的地图如何知道经过a和b的最短路呢?
- 1.2 也就是这个bfs会走”回头路”,但是又不是完全的回头路,因为走路的人的状态发生了变化,也就是手里多了钥匙,也就是bfs的变种:带状态(压缩)的bfs
- 1.3 那么就很容易想到,原来最普通的bfs判断是否走过就是只用了位置xy,那么现在我们多增加一个信息,也就是拥有的钥匙,那么该点没有走过变成了什么呢?那就是:该点位置没有走过,或者当前的拥有钥匙的状态在该点没有出现过
- 1.4 有了1.3我们就很容易知道,用什么数据结构去存顶点是否被访问啦: map<pair<int, int>, set
> seenKey; 左边是该点的位置,右边是该点所经历过的所有钥匙的集合 - 1.5 还需要考虑如何记录路径长度:map<pair<pair<int, int>, int>, int> dis; 很显然,左侧是<xy,key>,右侧代表了xy在key情况下的路径长度
- 1.6 考虑一个具体[“@a.”,”bAB”]的例子即可:
1
2
3
4
5
6
7
8
9
10
11
12cur: 0,0 -> 000000
next: 0,1 charis a-> 000001
next: 1,0 charis b-> 000010
cur: 0,1 -> 000001
next: 0,2 charis .-> 000001
next: 1,1 charis A-> 000001
next: 0,0 charis @-> 000001
cur: 1,0 -> 000010
next: 0,0 charis @-> 000010
cur: 0,2 -> 000001
cur: 1,1 -> 000001
next: 1,0 charis b-> 000011
1 | class Solution { |