引导
DFS关键字:递归/栈 + 所有解
DFS实现重要依赖于堆栈/递归 ,较为简单的解决了如何遍历所有元素,以及寻求“终点”的问题。
但是,DFS虽然可以查找到到达路径,但是却找不到最短的路径,针对这一问题,给出了BFS(广度优先遍历)的算法。
与DFS不同的是,这次不再是每个分叉路口一个一个走了,而是全部,同时遍历,直到找到终点,所对应的“层数”便是最短路径所需要的步数
上述解法当中,如果需要对当前队列中所有元素处理(即需要有层次的概念时),需要加入for(i<size)的循环
如果一次只处理队列中的一个元素(无层次概念),可以不用加for循环
BFS通用解法
int BFS(Node root, Node target) {
Queue<TreeNode> queue = new ArrayDeque<>(); // 建立队列
int step = 0; // 建立行动步数,通常用这个作为最短路径的返回值
// initialize
queue.add(root);
// BFS
while (!queue.isEmpty()) {
step = step + 1;
// 记录此时的队列大小,也即此层元素的多少
int size = queue.size();
for (int i = 0; i < size; ++i) {
//遍历此层的所有元素
Node cur = queue.poll();
return step if cur is target;
for (Node next : the neighbors of cur) {
queue.offer(next); //加入查找的方向,将下一层元素加入到队列中
}
}
}
return -1; // 没有找到目标返回-1
}
例题
102,树的层次遍历:标准解法
参考:
https://leetcode-cn.com/problems/binary-tree-level-order-traversal/solution/bfs-de-shi-yong-chang-jing-zong-jie-ceng-xu-bian-l/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new LinkedList<>();
BFS(root, res);
return res;
}
private void BFS(TreeNode treeNode, List<List<Integer>> res) {
if (treeNode == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(treeNode);
while (!queue.isEmpty()) {
List<Integer> level = new LinkedList<>();
int n = queue.size(); // 由于下方queue会一直offer,所以这里只能int提前存储下size
for(int i = 0; i < n; i++) {
// 只是为了完成本层遍历,无实际作用
TreeNode tmp = queue.poll();
if(tmp != null) {
level.add(tmp.val);
queue.offer(tmp.left);
queue.offer(tmp.right);
}
}
if(level.size() > 0) {
res.add(level); // 由于上方的level每次都是新new的,因此无需下方这种写法
// res.add(new LinkedList<>(level)); // 这种常常用于level是传入进来的参数,防止堆污染
}
}
}
}
zigzag版本:双端队列
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;
}
207. 课程表:拓扑排序
https://leetcode-cn.com/problems/course-schedule/
拓扑排序:
解答:
https://leetcode-cn.com/problems/course-schedule/solution/ke-cheng-biao-by-leetcode-solution/
参考文章:图的存储结构与拓扑排序实现思路
https://www.jianshu.com/p/cd24cfb6c8d0
DFS版本解答:
List<List<Integer>> edges;
int[] visited; // 状态数组。0:未处理;1:正在搜索;2:已经完成
boolean valid = true; // 结果存储
public boolean canFinish(int numCourses, int[][] prerequisites) {
edges = new ArrayList<List<Integer>>();
for (int i = 0; i < numCourses; ++i) {
edges.add(new ArrayList<Integer>());
}
visited = new int[numCourses];
for (int[] info : prerequisites) {
edges.get(info[1]).add(info[0]); // 邻接表的构建,注意直接get.add
}
for (int i = 0; i < numCourses && valid; ++i) {
// 剪枝
if (visited[i] == 0) {
dfs(i);
}
}
return valid;
}
public void dfs(int u) {
visited[u] = 1; // 对当前处理位置做标记
for (int v: edges.get(u)) {
if (visited[v] == 0) {
dfs(v); // 所有的子节点都满足拓扑排序,则本次处理节点必然满足。
if (!valid) {
return; // 直接返回,同时保留了当前为1的状态
}
} else if (visited[v] == 1) {
valid = false;
return;
}
}
visited[u] = 2; // 当前处理完成:这里不再是清理标记而是替换为另一种状态
}
BFS版本解答;
class Solution {
List<List<Integer>> edges;
int[] indeg; // 入度表
public boolean canFinish(int numCourses, int[][] prerequisites) {
edges = new ArrayList<List<Integer>>();
for (int i = 0; i < numCourses; ++i) {
edges.add(new ArrayList<Integer>());
}
indeg = new int[numCourses];
for (int[] info : prerequisites) {
edges.get(info[1]).add(info[0]);
++indeg[info[0]];
}
Queue<Integer> queue = new LinkedList<Integer>();
for (int i = 0; i < numCourses; ++i) {
if (indeg[i] == 0) {
queue.offer(i); // 第一批入度为0的节点进入队列
}
}
int visited = 0;
while (!queue.isEmpty()) {
++visited;
int u = queue.poll();
for (int v: edges.get(u)) {
--indeg[v];
if (indeg[v] == 0) {
queue.offer(v); // 第二批入度为0的表进入队列
}
}
}
return visited == numCourses;
}
}
994. 腐烂的橘子:多路BFS
https://leetcode-cn.com/problems/rotting-oranges/solution/fu-lan-de-ju-zi-by-leetcode-solution/
多源广度优先搜索
class Solution {
// dr,dc 配合使用得到 grid[r][c] 上grid[r-1][c]左grid[r][c-1]下grid[r+1][c]右grid[r][c+1]的元素
int[] dr = new int[]{
-1, 0, 1, 0}; // 代指四个方向,左[-1,0],上[0,-1],右[1,0],下[0,1]
int[] dc = new int[]{
0, -1, 0, 1};
public int orangesRotting(int[][] grid) {
// 获取二维数组的行数row 和 列数 column
int R = grid.length, C = grid[0].length;
// queue : all starting cells with rotten oranges
Queue<Integer> queue = new ArrayDeque();
Map<Integer, Integer> depth = new HashMap();
for (int r = 0; r < R; ++r)
for (int c = 0; c < C; ++c)
if (grid[r][c] == 2) {
int code = r * C + c; // 转化为索引唯一的一维数组
queue.add(code); //存储腐烂橘子
depth.put(code, 0); //存储橘子变为腐烂时的时间,key为橘子的一维数组下标,value为变腐烂的时间
}
int ans = 0; // 存储最终结果
while (!queue.isEmpty()) {
// 处理所有感染者
int code = queue.remove();
int r = code / C, c = code % C;
for (int k = 0; k < 4; ++k) {
// grid位置获取方式,四个方向,四种策略
int nr = r + dr[k];
int nc = c + dc[k];
if (0 <= nr && nr < R && 0 <= nc && nc < C && grid[nr][nc] == 1) {
// 0 <= nr && nr < R && 0 <= nc && nc < C,代指在grid有效范围之内,grid[nr][nc] == 1新鲜要处理
grid[nr][nc] = 2;
int ncode = nr * C + nc;
queue.add(ncode); // 入队
// 计次的关键 元素 grid[r][c] 的上左下右元素得腐烂时间应该一致
depth.put(ncode, depth.get(code) + 1);
ans = depth.get(ncode);
}
}
}
//检查grid,此时的grid能被感染已经都腐烂了,此时还新鲜的橘子无法被感染
for (int[] row: grid)
for (int v: row)
if (v == 1)
return -1;
return ans;
}
}