广度优先搜索
1. 简介
BFS 的核心思想应该不难理解的,就是把一些问题抽象成图,从一个点开始,向四周开始扩散。一般来说,我们写 BFS 算法都是用「队列」这种数据结构,每次将一个节点周围的所有节点加入队列。
BFS 相对 DFS 的最主要的区别是:BFS 找到的路径一定是最短的,但代价就是空间复杂度可能比 DFS 大很多。
序号 | 题目 | 难度 |
---|---|---|
1 | 752. 打开转盘锁 | 中等 |
2 | 127. 单词接龙 | 困难 |
BFS 框架模板如下:
// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
Queue<Node> q; // 核心数据结构
Set<Node> visited; // 避免走回头路
q.offer(start); // 将起点加入队列
visited.add(start);
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);
}
}
}
}
// 如果走到这里,说明在图中没有找到目标节点
}
2. 应用
2.1. Leetcode 752. 打开转盘锁
2.1.1. 题目
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为 '0','0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,你需要给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。
示例 1:
输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
输出:6
解释:
可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,
因为当拨动到 "0102" 时这个锁就会被锁定。
2.1.2. 解题思路
使用广度优先搜索的思想,从起点开始枚举邻近的数字组合,枚举过程需要跳过死亡数字和已经访问过的数字,直到找到密码为止。
2.1.3. 代码实现
class Solution {
private static final String START = "0000";
private static final int SUCCESS = 0;
private static final int FAIL = -1;
public int openLock(String[] deadends, String target) {
if (target.equals(START)) {
return SUCCESS;
}
Set<String> deadNumbers = new HashSet<>(Arrays.asList(deadends));
if (deadNumbers.contains(START)) {
return FAIL;
}
Set<String> visit = new HashSet<>();
Queue<String> queue = new LinkedList<>();
queue.offer(START);
visit.add(START);
int step = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
String currentNumber = queue.poll();
for (String number : nextNumber(currentNumber)) {
if (visit.contains(number) || deadNumbers.contains(number)) {
continue;
}
if (target.equals(number)) {
return step + 1;
}
queue.offer(number);
visit.add(number);
}
}
step += 1;
}
return -1;
}
private List<String> nextNumber(String number) {
List<String> result = new ArrayList<>();
for (int i = 0; i < number.length(); i++) {
result.add(numberOperate(number.toCharArray(), i, 1));
result.add(numberOperate(number.toCharArray(), i, -1));
}
return result;
}
private String numberOperate(char[] state, int i, int value) {
int newVal = state[i] - '0' + value;
if (newVal == 10) {
state[i] = '0';
} else if (newVal == -1) {
state[i] = '9';
} else {
state[i] = (char) (newVal + '0');
}
return new String(state);
}
}
2.2. Leetcode 127. 单词接龙
2.2.1. 题目
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk:
每一对相邻的单词只差一个字母。
对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
sk == endWord
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。
示例 1:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
2.2.2. 解题思路
这里,我们可以使用广度优先搜索的思路,对于每一个单词,我们都可以通过找到它的邻近单词。
即对于当前单词的每一位,都可以将其替换为 \(a - z\) 范围内的其他字母,这样就找到了当前单词的邻近的单词。
2.2.3. 代码实现
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Set<String> canUseWords = new HashSet<>(wordList);
Set<String> visit = new HashSet<>();
Queue<String> queue = new LinkedList<>();
queue.offer(beginWord);
visit.add(beginWord);
int step = 1;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
String candidate = queue.poll();
for (String word : nextWord(candidate)) {
if (visit.contains(word) || !canUseWords.contains(word)) {
continue;
}
if (word.equals(endWord)) {
return step + 1;
}
queue.offer(word);
visit.add(word);
}
}
step++;
}
return 0;
}
private List<String> nextWord(String word) {
List<String> result = new ArrayList<>();
for (int i = 0; i < word.length(); i++) {
for (char j = 'a'; j <= 'z'; j++) {
if (j == word.charAt(i)) {
continue;
}
char[] status = word.toCharArray();
status[i] = j;
result.add(new String(status));
}
}
return result;
}
}
参考: