BFS
(1)BFS的问题一般都会选用队列方式实现;
(2)代码模板如下:
void BFS()
{
定义队列;
定义备忘录,用于记录已经访问的位置;
判断边界条件,是否能直接返回结果的。
将起始位置加入到队列中,同时更新备忘录。
while (队列不为空) {
获取当前队列中的元素个数。
for (元素个数) {
取出一个位置节点。
判断是否到达终点位置。
获取它对应的下一个所有的节点。
条件判断,过滤掉不符合条件的位置。
新位置重新加入队列。
}
}
}
一、二进制矩阵中的最短路径
在一个 N × N 的方形网格中,每个单元格有两种状态:空(0)或者阻塞(1)。
一条从左上角到右下角、长度为 k 的畅通路径,由满足下述条件的单元格 C_1, C_2, …, C_k 组成:
相邻单元格 C_i 和 C_{i+1} 在八个方向之一上连通(此时,C_i 和 C_{i+1} 不同且共享边或角)
C_1 位于 (0, 0)(即,值为 grid[0][0])
C_k 位于 (N-1, N-1)(即,值为 grid[N-1][N-1])
如果 C_i 位于 (r, c),则 grid[r][c] 为空(即,grid[r][c] == 0)
返回这条从左上角到右下角的最短畅通路径的长度。如果不存在这样的路径,返回 -1 。
解:
struct Node {
int x;
int y;
};
class Solution {
public:
int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
int ans = 0;
queue<Node> myQ; // BFS一般通过队列方式解决
int M = grid.size();
int N = grid[0].size();
// 先判断边界条件,很明显,这两种情况下都是不能到达终点的。
if (grid[0][0] == 1 || grid[M - 1][N - 1] == 1) {
return -1;
}
// 备忘录,记录已经走过的节点
vector<vector<int>> mem(M, vector<int>(N, 0));
myQ.push({0, 0});
mem[0][0] = 1;
// 以下是标准BFS的写法
while (!myQ.empty()) {
int size = myQ.size();
for (int i = 0; i < size; i++) {
Node currentNode = myQ.front();
int x = currentNode.x;
int y = currentNode.y;
// 判断是否满足退出的条件
if (x == (N - 1) && y == (M - 1)) {
return (ans + 1);
}
// 下一个节点所有可能情况
vector<Node> nextNodes = {{x + 1, y}, {x - 1, y}, {x + 1, y - 1}, {x + 1, y + 1},
{x, y + 1}, {x, y - 1}, {x - 1, y - 1}, {x - 1, y + 1}};
for (auto& n : nextNodes) {
// 过滤条件1: 边界检查
if (n.x < 0 || n.x >= N || n.y < 0 || n.y >= M) {
continue;
}
// 过滤条件2:备忘录检查
if (mem[n.y][n.x] == 1) {
continue;
}
// 过滤条件3:题目中的要求
if (grid[n.y][n.x] == 1) {
continue;
}
// 通过过滤筛选,加入队列!
mem[n.y][n.x] = 1;
myQ.push(n);
}
myQ.pop();
}
ans++;
}
return -1;
}
};
二、完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
解:
public class NumSquares {
private class Node {
int val;
int step;
public Node(int val, int step) {
this.val = val;
this.step = step;
}
}
public int numSquares(int n) {
Queue<Node> queue = new LinkedList<>();
queue.add(new Node(n, 1));
boolean record[] = new boolean[n];
while (!queue.isEmpty()) {
int val = queue.peek().val;
int step = queue.peek().step;
queue.remove();
// 每一层的广度遍历
for (int i = 1;; i++) {
int nextVal = val - i * i;
// 说明已到最大平方数
if (nextVal < 0)
break;
// 由于是广度遍历,所以当遍历到0时,肯定是最短路径
if(nextVal == 0)
return step;
// 当再次出现时没有必要加入,因为在该节点的路径长度肯定不小于第一次出现的路径长
if(!record[nextVal]){
queue.add(new Node(nextVal,step + 1));
record[nextVal] = true;
}
}
}
return -1;
}
public static void main(String[] args) {
System.out.println(new NumSquares().numSquares(13));
}
}
三、单词接龙
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
解:
对给定的 wordList 做预处理,找出所有的通用状态。将通用状态记录在字典中,键是通用状态,值是所有具有通用状态的单词。
将包含 beginWord 和 1 的元组放入队列中,1 代表节点的层次。我们需要返回 endWord 的层次也就是从 beginWord 出发的最短距离。
为了防止出现环,使用访问数组记录。
当队列中有元素的时候,取出第一个元素,记为 current_word。
找到 current_word 的所有通用状态,并检查这些通用状态是否存在其它单词的映射,这一步通过检查 all_combo_dict 来实现。
从 all_combo_dict 获得的所有单词,都和 current_word 共有一个通用状态,所以都和 current_word 相连,因此将他们加入到队列中。
对于新获得的所有单词,向队列中加入元素 (word, level + 1) 其中 level 是 current_word 的层次。
最终当你到达期望的单词,对应的层次就是最短变换序列的长度
import javafx.util.Pair;
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
// Since all words are of same length.
int L = beginWord.length();
// Dictionary to hold combination of words that can be formed,
// from any given word. By changing one letter at a time.
HashMap<String, ArrayList<String>> allComboDict = new HashMap<String, ArrayList<String>>();
wordList.forEach(
word -> {
for (int i = 0; i < L; i++) {
// Key is the generic word
// Value is a list of words which have the same intermediate generic word.
String newWord = word.substring(0, i) + '*' + word.substring(i + 1, L);
ArrayList<String> transformations =
allComboDict.getOrDefault(newWord, new ArrayList<String>());
transformations.add(word);
allComboDict.put(newWord, transformations);
}
});
// Queue for BFS
Queue<Pair<String, Integer>> Q = new LinkedList<Pair<String, Integer>>();
Q.add(new Pair(beginWord, 1));
// Visited to make sure we don't repeat processing same word.
HashMap<String, Boolean> visited = new HashMap<String, Boolean>();
visited.put(beginWord, true);
while (!Q.isEmpty()) {
Pair<String, Integer> node = Q.remove();
String word = node.getKey();
int level = node.getValue();
for (int i = 0; i < L; i++) {
// Intermediate words for current word
String newWord = word.substring(0, i) + '*' + word.substring(i + 1, L);
// Next states are all the words which share the same intermediate state.
for (String adjacentWord : allComboDict.getOrDefault(newWord, new ArrayList<String>())) {
// If at any point if we find what we are looking for
// i.e. the end word - we can return with the answer.
if (adjacentWord.equals(endWord)) {
return level + 1;
}
// Otherwise, add it to the BFS Queue. Also mark it visited
if (!visited.containsKey(adjacentWord)) {
visited.put(adjacentWord, true);
Q.add(new Pair(adjacentWord, level + 1));
}
}
}
}
return 0;
}
}