来源:https://leetcode-cn.com/problems/open-the-lock/solution/wo-xie-liao-yi-tao-bfs-suan-fa-kuang-jia-jian-dao-/
又是看这个大佬学习的,下面我自己总结一下,并且记录一下遇到的使用BFS的题目,验证一下这个套路的可行性。一直比较讨厌这种题,因为没有总结出一套框架来。(现在回溯熟悉的差不多了 哈哈哈哈)
一.背景和框架
BFS本质:本质上就是一幅「图」,让你从一个起点,走到终点,问最短路径
下面是算法的框架:
// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
Queue<Node> q; // 核心数据结构
Set<Node> visited; // 避免走回头路
q.offer(start); // 将起点加入队列
visited.add(start);
int step = 0; // 记录扩散的步数
while (q not empty) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
Node cur = q.poll();
/* 划重点:这里判断是否到达终点 */
if (cur is target)
return step;
/* 将 cur 的相邻节点加入队列 */
for (Node x : cur.adj())
if (x not in visited) {
q.offer(x);
visited.add(x);
}
}
/* 划重点:更新步数在这里 */
step++;
}
}
二.例题
1.二叉树的最小高度
int BFS(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
// root 本身就是一层,depth 初始化为 1
int depth = 1;
while (!q.isEmpty()) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
TreeNode cur = q.poll();
/* 判断是否到达终点 */
if (cur.left == null && cur.right == null)
return depth;
/* cur 的相邻节点加入队列 */
if (cur.left != null)
q.offer(cur.left);
if (cur.right != null)
q.offer(cur.right);
}
/* 这里增加步数 */
depth++;
}
return depth;
}
解释:
- Queue 在Java中用LinkedList<>()来创建实例
- 这里不用加vistied记录走过的路径是因为树结构本身
- 判断是否达到终点:左子节点和右子节点均为null
2.腐烂的橘子
这道题的主要思路是:
- 一开始,我们找出所有腐烂的橘子,将它们放入队列,作为第 0 层的结点。
- 然后进行 BFS 遍历,每个结点的相邻结点可能是上、下、左、右四个方向的结点,注意判断结点位于网格边界的特殊情况。
- 由于可能存在无法被污染的橘子,我们需要记录新鲜橘子的数量。在 BFS 中,每遍历到一个橘子(污染了一个橘子),就将新鲜橘子的数量减一。如果 BFS 结束后这个数量仍未减为零,说明存在无法被污染的橘子。
public int BFS(int[][] grid) {
int M = grid.length;
int N = grid[0].length;
Queue<int[]> queue = new LinkedList<>();//这个T存的其实是腐烂橘子的坐标 跟Node一个道理
int count = 0; // count 表示新鲜橘子的数量
for (int r = 0; r < M; r++) {
for (int c = 0; c < N; c++) {
if (grid[r][c] == 1) {
count++;
} else if (grid[r][c] == 2) {
queue.add(new int[]{r, c});
}
}
}
int round = 0; // round 表示腐烂的轮数,或者分钟数
while (count > 0 && !queue.isEmpty()) {
int n = queue.size();
for (int i = 0; i < n; i++) {
int[] orange = queue.poll();
int r = orange[0];//横坐标
int c = orange[1];//纵坐标
if (r-1 >= 0 && grid[r-1][c] == 1) {
grid[r-1][c] = 2;
count--;
queue.add(new int[]{r-1, c});
}
if (r+1 < M && grid[r+1][c] == 1) {
grid[r+1][c] = 2;
count--;
queue.add(new int[]{r+1, c});
}
if (c-1 >= 0 && grid[r][c-1] == 1) {
grid[r][c-1] = 2;
count--;
queue.add(new int[]{r, c-1});
}
if (c+1 < N && grid[r][c+1] == 1) {
grid[r][c+1] = 2;
count--;
queue.add(new int[]{r, c+1});
}
}
round++;
}
if (count > 0) {
return -1;
} else {
return round;
}
}
3.地图分析
代码如下:
public int maxDistance(int[][] grid) {
int N = grid.length;
Queue<int[]> queue = new LinkedList<>();
// 将所有的陆地格子加入队列
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (grid[i][j] == 1) {
queue.add(new int[]{i, j});
}
}
}
// 如果我们的地图上只有陆地或者海洋,请返回 -1。
if (queue.isEmpty() || queue.size() == N * N) {
return -1;
}
int distance = -1;
while (!queue.isEmpty()) {
distance++;
int n = queue.size();
// 这里一口气取出 n 个结点,以实现层序遍历
for (int i = 0; i < n; i++) {
int[] cell = queue.poll();
int r = cell[0];
int c = cell[1];
// 遍历上方单元格
if (r-1 >= 0 && grid[r-1][c] == 0) {
grid[r-1][c] = 2;
queue.add(new int[]{r-1, c});
}
// 遍历下方单元格
if (r+1 < N && grid[r+1][c] == 0) {
grid[r+1][c] = 2;
queue.add(new int[]{r+1, c});
}
// 遍历左边单元格
if (c-1 >= 0 && grid[r][c-1] == 0) {
grid[r][c-1] = 2;
queue.add(new int[]{r, c-1});
}
// 遍历右边单元格
if (c+1 < N && grid[r][c+1] == 0) {
grid[r][c+1] = 2;
queue.add(new int[]{r, c+1});
}
}
}
return distance;
}
题目问的是「距离陆地区域最远的海洋区域」,怎么理解呢:
- 其实就是从陆地(1)开始,要扩散多少次,才能把所有的海洋给覆盖掉。「最远」应该从这个角度来理解。
而「该海洋区域到离它最近的陆地区域的距离」,怎么理解呢:
- 最近是因为:BFS的搜索都是一层一层的,如果这个陆地是最近的,那么它一定是先扩散到这个海洋。
解释:
- 一开始,我们找出所有陆地格子,将它们放入队列,作为第 0 层的结点。为什么要找陆地,不把海洋放到队列中呢,因为题目是要找出这个最远海洋的坐标,所以肯定是通过陆地出发去找,所以就把陆地坐标加入到队列
- 然后进行 BFS 遍历,每个结点的相邻结点可能是上、下、左、右四个方向的结点,注意判断结点位于网格边界的特殊情况。
- 当遍历结束时,当前的遍历层数就是海洋格子到陆地格子的最远距离。