一. BFS及0-1BFS的简单介绍
深度优先搜索DFS和广度优先搜索BFS是经常使用的搜索算法,在各类题目中都有广泛的应用。
深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次。
广度优先搜索算法(Breadth-First Search,缩写为 BFS),又称为宽度优先搜索,是一种图形搜索算法。简单的说,BFS 是从根结点开始,沿着树的宽度遍历树的结点。如果所有结点均被访问,则算法中止。
一般来说,能用DFS的,一般都可以用BFS解决,反过来同理。但是不同的题目,对于DFS和BFS的复杂度可能是有些差别的,对于有些题目,用BFS更容易解决,会有更少的时间复杂度。
在上文:
BFS (Java) 广度优先搜索 简单介绍、模板、案例(一)
中,给出了BFS的简单介绍,模板和相关案例。那么在本文中,主要来介绍0-1BFS,或者说双端队列BFS。对于简单的BFS类题目,我们在队列尾部进行一直添加即可,但有些题目则不能满足需求,需要在头尾进行添加或者删除元素,我们称作0-1BFS或者双端队列BFS,而对于头尾的操作,可能是不同的,比如头不增加深度,而尾增加深度。
二. 简单模板
class Solution {
public BFS(TreeNode root) {
//双端队列,用来存储元素
Deque<TreeNode> queue = new ArrayDeque<>();
//添加首个元素
queue.add(首个元素);
//当队列不为空一直进行循环,直到队列不再有元素
while(!queue.isEmpty()){
if(限制条件){
头操作,更新深度,或者相反;
}
else{
尾操作,不更新深度,或者相反;
}
}
返回答案;
}
}
三. 案例
leetcode 1263 推箱子
「推箱子」是一款风靡全球的益智小游戏,玩家需要将箱子推到仓库中的目标位置。
游戏地图用大小为 m x n 的网格 grid 表示,其中每个元素可以是墙、地板或者是箱子。
现在你将作为玩家参与游戏,按规则将箱子 'B' 移动到目标位置 'T' :
玩家用字符 'S' 表示,只要他在地板上,就可以在网格中向上、下、左、右四个方向移动。
地板用字符 '.' 表示,意味着可以自由行走。
墙用字符 '#' 表示,意味着障碍物,不能通行。
箱子仅有一个,用字符 'B' 表示。相应地,网格上有一个目标位置 'T'。
玩家需要站在箱子旁边,然后沿着箱子的方向进行移动,此时箱子会被移动到相邻的地板单元格。记作一次「推动」。
玩家无法越过箱子。
返回将箱子推到目标位置的最小 推动 次数,如果无法做到,请返回 -1。
输入:grid = [["#","#","#","#","#","#"],
["#","T","#","#","#","#"],
["#",".",".","B",".","#"],
["#",".","#","#",".","#"],
["#",".",".",".","S","#"],
["#","#","#","#","#","#"]]
输出:3
解释:我们只需要返回推箱子的次数。
class Solution {
int row;
int col;
char[][] grid;
public int minPushBox(char[][] grid) {
//初始化
this.grid = grid;
row = grid.length;
col = grid[0].length;
int pi = 0, pj = 0, bi = 0, bj = 0;
for(int i = 0; i < row; i++){
for(int j = 0; j < col; j++){
if(grid[i][j] == 'S'){
pi = i;
pj = j;
}
if(grid[i][j] == 'B'){
bi = i;
bj = j;
}
}
}
int[] dirs = new int[]{-1,0,1,0,-1};
Deque<int[]> q = new ArrayDeque<>();
boolean[][] vis = new boolean[row*col][row*col];
q.offer(new int[]{get(pi, pj), get(bi,bj), 0});
vis[get(pi,pj)][get(bi,bj)] = true;
while(!q.isEmpty()){
var p = q.poll();
int d = p[2];
bi = p[1]/col;
bj = p[1]%col;
pi = p[0]/col;
pj = p[0]%col;
if(grid[bi][bj] == 'T'){
return d;
}
for(int k = 0; k < 4; k++){
int px = pi + dirs[k];
int py = pj + dirs[k+1];
if(!check(px,py)){
continue;
}
if(px == bi && py == bj){
int bx = bi + dirs[k];
int by = bj + dirs[k+1];
if(!check(bx,by) || vis[get(px,py)][get(bx,by)]){
continue;
}
vis[get(px,py)][get(bx,by)] = true;
q.offer(new int[]{get(px,py), get(bx,by), d+1});
}
else if(!vis[get(px,py)][get(bi, bj)]){
vis[get(px,py)][get(bi, bj)] = true;
q.offerFirst(new int[]{get(px,py), get(bi,bj), d});
}
}
}
return -1;
}
public int get(int i, int j){//映射
return i*col + j;
}
public boolean check(int i, int j){//检查坐标是否合规
return i >= 0 && i < row && j >= 0 && j < col && grid[i][j] != '#';
}
}
本题小结:(1)判断新位置{px,py}和箱子{bi,bj}位置是否相同,相同则证明能推动箱子
(2)推动箱子,则深度+1,即对应推动次数,添加至尾部
(3)未推动箱子,深度不变,添加至头部
练习题目:
leetcode 1368. 使网格图至少有一条有效路径的最小代价
leetcode 2290. 到达角落需要移除障碍物的最小数目
参考来源
[1] leetcode ylb [Python3/Java/C++/Go/TypeScript] 一题一解:双端队列 BFS(清晰题解)