BFS 广度优先搜索的本质是一幅「图」从一个 start 起点走到 target 终点,问最短路径

BFS 可以找到最短距离,但是空间复杂度高,而 DFS 的空间复杂度较低

BFS、DFS 模板

for len(queue) > 0 {
	for i, node := range queue {
		queue.push(未访问过的结点)
	}
	queue = queue[len(queue):]
}

dfs = func(node *TreeNode) {
	if node == nil { return } // 递归终止条件
	// 前序遍历 操作数据
	dfs(node.Left)
	dfs(node.Right)
}
// 计算从 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++;
		}
}

111. Minimum Depth of Binary Tree

int minDepth(TreeNode root) {
	if (root == null) return 0;
	Queue<TreeNode> q = new LinkedList<>();
	q.offer(root);
	int depth = 1; // root 本身即一层,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;
}

注意两个循环的配合,外层 while 控制一层层往下走,内层 for 循环利用 sz 控制从左到右遍历每一层二叉树结点(遍历过程中队列 q 控制出列

Untitled

752. Open the Lock

带四个圆形拨轮的转盘锁,穷举其密码组合,每个位置可以向上或向下,即一共八种可能,如从 "0000" 开始,转一次可以有 "1000", "9000", "0100", "0900"... 8 种密码,可以抽象成一幅图,图中每个结点有 8 个相邻结点,求最短距离

int openLock(String[] deadends, String target) {
    // 记录需要跳过的死亡密码
    Set<String> deads = new HashSet<>();
    for (String s : deadends) deads.add(s);
    // 记录已经穷举过的密码,防止走回头路
    Set<String> visited = new HashSet<>();
    Queue<String> q = new LinkedList<>();
    // 从起点开始启动广度优先搜索
    int step = 0;
    q.offer("0000");
    visited.add("0000");
    
    while (!q.isEmpty()) {
        int sz = q.size();
        /* 将当前队列中的所有节点向周围扩散 */
        for (int i = 0; i < sz; i++) {
            String cur = q.poll();
            
            /* 判断是否到达终点 */
            if (deads.contains(cur))
                continue;
            if (cur.equals(target))
                return step;
            
            /* 将一个节点的未遍历相邻节点加入队列 */
            for (int j = 0; j < 4; j++) {
                String up = plusOne(cur, j);
                if (!visited.contains(up)) {
                    q.offer(up);
                    visited.add(up);
                }
                String down = minusOne(cur, j);
                if (!visited.contains(down)) {
                    q.offer(down);
                    visited.add(down);
                }
            }
        }
        /* 在这里增加步数 */
        step++;
    }
    // 如果穷举完都没找到目标密码,那就是找不到了
    return -1;
}
func openLock(deadends []string, target string) int {
	if target == "0000" {
		return 0
	}
	visited := map[string]bool{"0000": true}
	for _, s := range deadends {
		visited[s] = true
	}
	step := 0
	q := []string{"0000"}
	
	for len(q) > 0 {
		sz := len(q)
		for i := 0; i < sz; i++ {
			cur := q[0]
			q = q[1:]

			if cur == target {
				return step
			}

			// 将一个结点的未遍历相邻结点加入队列
			for j := 0; j < 4; j++ {
				up := plusOne(cur, j)
				if !visited[up] {
					q = append(q, up)
					visited[up] = true
				}
				down := minusOne(cur, j)
				if !visited[down] {
					q = append(q, down)
					visited[down] = true
				}
			}
		}
		step++
	}
	return -1
}

// 将 s[j] 向上拨动一次
func plusOne(s string, j int) string {
	chars := []rune(s) // '0000' -> [48, 48, 48, 48]
	if chars[j] == '9' {
		chars[j] = '0'
	} else {
		chars[j] += 1
	}
	return string(chars)
}

// 向下拨动一次
func minusOne(s string, j int) string {
	chars := []rune(s)
	if chars[j] == '0' {
		chars[j] = '9'
	} else {
		chars[j] -= 1
	}
	return string(chars)
}

双向 BFS

单向 BFS 框架从起点开始向四周扩散,遇到终点时停止;双向 BFS 从起点和终点同时开始扩散,当两边有交集的时候停止

int openLock(String[] deadends, String target) {
    Set<String> deads = new HashSet<>();
    for (String s : deadends) deads.add(s);
    // 用集合不用队列,可以快速判断元素是否存在
    Set<String> q1 = new HashSet<>();
    Set<String> q2 = new HashSet<>();
    Set<String> visited = new HashSet<>();
    
    int step = 0;
    q1.add("0000");
    q2.add(target);
    
    while (!q1.isEmpty() && !q2.isEmpty()) {
        // 哈希集合在遍历的过程中不能修改,用 temp 存储扩散结果
        Set<String> temp = new HashSet<>();

        /* 将 q1 中的所有节点向周围扩散 */
        for (String cur : q1) {
            /* 判断是否到达终点 */
            if (deads.contains(cur))
                continue;
            if (q2.contains(cur))
                return step;
            visited.add(cur);

            /* 将一个节点的未遍历相邻节点加入集合 */
            for (int j = 0; j < 4; j++) {
                String up = plusOne(cur, j);
                if (!visited.contains(up))
                    temp.add(up);
                String down = minusOne(cur, j);
                if (!visited.contains(down))
                    temp.add(down);
            }
        }
        /* 在这里增加步数 */
        step++;
        // temp 相当于 q1
        // 这里交换 q1 q2,下一轮 while 就是扩散 q2
        q1 = q2;
        q2 = temp;
    }
    return -1;
}

双向 BFS 不再使用队列,而用 HashSet 快速判断两个集合是否有交集