1 BFS
最关键的是:
- bfs本身的特性,一个是按照层数往下遍历,一个是可以决定是否遍历完当前层再往下遍历
- bfs的队列初始化即为bfs的搜索起点,然后注意状态问题,当每个节点带有状态以后,bfs的visited变量就不仅仅是node的编号,应该是node的编号 + “当前状态”,具体参考(0864,0847)
- 注意:遍历当前层,需要把size记录成临时变量,因为curLevel会加入新元素
- 注意:入队后就需要置为visited,否则可能重复入队
bfs的遍历本质上可以看成当前点,到所有后继节点的可能跳转的状态。(0733例题)
bfs的核心代码:
int bfsCheck(vector<vector<int>>& oriBoard) {
queue<vector<vector<int>>> curLevel;
curLevel.push(oriBoard);
vis.insert(toInt(oriBoard));
int depth = 1;
while(!curLevel.empty()) {
int curSize = curLevel.size();
// pop all curLevel and do next level
while(curSize-- > 0) { // 最关键的,一定不要直接写成curLevel.size,因为curLevel会放后面的节点的呜呜呜
auto board = curLevel.front();
curLevel.pop();
auto coord = getPos(board);
int x = coord.first;
int y = coord.second;
for(int mv = 0; mv < 4; ++mv) {
int nextX = x + dx[mv];
int nextY = y + dy[mv];
if(0 <= nextX && nextX < 2 && 0 <= nextY && nextY < 3) {
swap(board[nextX][nextY], board[x][y]);
// cout << "trying : " << x << ", " << y << " to " << nextX << ", " << nextY << " withd d = " << depth <<endl;
// print(board);
int intBoard = toInt(board);
if(0 == vis.count(intBoard)) {
curLevel.push(board);
if(isTar(board)) {
// cout << "final tar: " << endl;
// print(board);
return depth;
}
vis.insert(intBoard);
}
swap(board[x][y], board[nextX][nextY]);
}
}
}
++depth;
}
2 例题
0733slidingPuzzle 滑动到123450谜题
1 题目
https://leetcode.cn/problems/sliding-puzzle/
2 解题思路
- 1 解题思路:
- 1.1 为何选用bfs,因为要获得最终的移动步数,那么bfs的层数就是最终的答案
- 1.2 节点就是当前的棋盘,后继节点为移动后改变的棋盘
- 1.3 使用位运算加速
class Solution {
public:
constexpr static int tar = ((1<<3) | (2<<6) | (3<<9) | (4<<12) | (5<<15));
unordered_set<int> vis;
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, -1, 0, 1};
bool isTar(vector<vector<int>>& board) {
return tar == toInt(board);
}
int toInt(vector<vector<int>>& board) {
return board[0][0]<<3 | board[0][1]<<6 | board[0][2]<<9 | board[1][0]<<12 | board[1][1]<<15;
}
pair<int, int> getPos(vector<vector<int>>& board) {
for(int i = 0; i < 2; ++i) {
for(int j = 0; j < 3; ++j) {
if(board[i][j] == 0) {
return {i, j};
}
}
}
return {-1, -1};
}
int slidingPuzzle(vector<vector<int>>& board) {
int res = 0;
if(toInt(board) == tar) {
return 0;
}
res = bfsCheck(board);
return res;
}
void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
int bfsCheck(vector<vector<int>>& oriBoard) {
queue<vector<vector<int>>> curLevel;
curLevel.push(oriBoard);
vis.insert(toInt(oriBoard));
int depth = 1;
while(!curLevel.empty()) {
int curSize = curLevel.size();
// pop all curLevel and do next level
while(curSize-- > 0) {
auto board = curLevel.front();
curLevel.pop();
auto coord = getPos(board);
int x = coord.first;
int y = coord.second;
for(int mv = 0; mv < 4; ++mv) {
int nextX = x + dx[mv];
int nextY = y + dy[mv];
if(0 <= nextX && nextX < 2 && 0 <= nextY && nextY < 3) {
swap(board[nextX][nextY], board[x][y]);
// cout << "trying : " << x << ", " << y << " to " << nextX << ", " << nextY << " withd d = " << depth <<endl;
// print(board);
int intBoard = toInt(board);
if(0 == vis.count(intBoard)) {
curLevel.push(board);
if(isTar(board)) {
// cout << "final tar: " << endl;
// print(board);
return depth;
}
vis.insert(intBoard);
}
swap(board[x][y], board[nextX][nextY]);
}
}
}
++depth;
}
return -1;
}
void print(vector<vector<int>>& board) {
for(int i = 0; i < 2; ++i) {
for(int j = 0; j < 3; ++j) {
cout << board[i][j] << " ";
}cout << " | ";
}
cout << "----------\n";
}
};
0675golfCutTree 高尔夫砍树
1 题目
https://leetcode.cn/problems/cut-off-trees-for-golf-event/
2 解题思路
- 1 解题思路:
- 1.1 使用优先队列将所有树从小到高排序,存为trees
- 1.2 每次从trees中取出一个树,对其进行bfs直到找到trees的下一个目标
- 2 bfs思路:
- 2.1 queue初始化为起点
- 2.2 对于queue中所有元素,加入当前queue的所有后继,加入以后就把元素置为visited
- 2.3 进入下一个level
- 3 为什么加入的过程中就要吧元素置为vis?因为:假设1层有a,b, 2层有c,c为ab的邻居,那么遍历第1层的时候,对于ab的后继,到a,加入c,若不置为vis,则b会再加入一遍,导致bfs很慢!
class Solution {
public:
int m;
int n;
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, -1, 0, 1};
int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int cutOffTree(vector<vector<int>>& forest) {
// get all trees
m = forest.size();
n = forest[0].size();
auto cmp = [](const pair<pair<int, int>, int>& a, const pair<pair<int, int>, int>& b) {
return a.second > b.second;
};
priority_queue<pair<pair<int, int>, int>, vector<pair<pair<int, int>, int>>, decltype(cmp)>trees (cmp);
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(forest[i][j] > 1) {
trees.push({{i, j}, forest[i][j]});
}
}
}
int curX = 0, curY = 0;
int tarX = -1, tarY = -1;
int res = 0;
while(!trees.empty()) {
auto tree = trees.top();
trees.pop();
tarX = tree.first.first;
tarY = tree.first.second;
// judge if we can walk to x, y
// cout << "from/to: " << curX << "," << curY << " -> " << tarX << "," << tarY << endl;
if(!tryWalk(forest, curX, curY, tarX, tarY, res)) {
return -1;
} else {
// cut tarX, tarY
forest[tarX][tarY] = 1;
curX = tarX;
curY = tarY;
}
// auto step = bfs(forest, curX, curY, tarX, tarY);
// if(-1 == step) {
// return -1;
// } else {
// // cut tarX, tarY
// forest[tarX][tarY] = 1;
// res+= step;
// curX = tarX;
// curY = tarY;
// }
}
return res;
}
int bfs(vector<vector<int>>& forest, int sx, int sy, int tx, int ty) {
if (sx == tx && sy == ty) {
return 0;
}
int row = forest.size();
int col = forest[0].size();
int step = 0;
queue<pair<int, int>> qu;
vector<vector<bool>> visited(row, vector<bool>(col, false));
qu.emplace(sx, sy);
visited[sx][sy] = true;
while (!qu.empty()) {
step++;
int sz = qu.size();
for (int i = 0; i < sz; ++i) {
auto [cx, cy] = qu.front();
qu.pop();
for (int j = 0; j < 4; ++j) {
int nx = cx + dirs[j][0];
int ny = cy + dirs[j][1];
if (nx >= 0 && nx < row && ny >= 0 && ny < col) {
if (!visited[nx][ny] && forest[nx][ny] > 0) {
if (nx == tx && ny == ty) {
return step;
}
qu.emplace(nx, ny);
visited[nx][ny] = true;
}
}
}
}
}
return -1;
}
// using bfs from <curX, curY> to <tarX, tarY>, the depth of bfs should be the distance
bool tryWalk(vector<vector<int>>& forest, int curX, int curY, int tarX, int tarY, int& res) {
// bfs
queue<pair<int, int>> curLevel;
vector<vector<int>> vis(m, vector<int>(n, false));
curLevel.push({curX, curY});
vis[curX][curY] = true;
int depth = 0;
while(!curLevel.empty()) {
// queue<pair<int, int>> nextLevel;
// while(!curLevel.empty()) {
int curLevelSize = curLevel.size();
while(curLevelSize-- > 0) {
auto curNode = curLevel.front();
curLevel.pop();
if(curNode == pair<int, int>{tarX, tarY}) {
// update res
res += depth;
return true;
}
for(int mv = 0; mv < 4; ++mv) {
int nextX = curNode.first + dx[mv];
int nextY = curNode.second + dy[mv];
if(0 <= nextX && nextX < m && 0 <= nextY && nextY < n && 0 != forest[nextX][nextY] && !vis[nextX][nextY]) {
curLevel.push({nextX, nextY});
vis[nextX][nextY] = true;
// cout << "nextNode: " << curNode.first << "," << curNode.second << endl;
}
}
}
// curLevel = std::move(nextLevel);
++depth;
}
return false;
}
};
0847allTravelShortestPath 访问所有节点最短路径
1 题目
https://leetcode.cn/problems/shortest-path-visiting-all-nodes/
2 解题思路
- 1 解题思路:
- 1.1 使用三元组 <u, mask, dist>表示,当前节点u的mask的搜索情况对应的搜索距离dist,调用bfs即可,但是对于下一个v,我们需要检测: v节点的1 << v | mask这种访问情况是否被访问过
- 1.2 如何考虑这个方法?
- 1.2.1 初始化的时候所有节点都加入队里(认为可以从每个节点出发)
- 1.2.2 bfs的具体过程中,退出条件就是 队首的mask是否标记了所有节点都被访问
- 1.3 这个方法为什么可以?
- 一句话:这个方法:利用bfs,按照路径长度从小到大遍历了所有的可能路径(队列初始化有所有的顶点就是这个意思),比如初始化,就是说将长度为0的所有可能路径遍历完成,然后下一层bfs会将所有长度为1的可能路径遍历完成,这个路径的记录方式为mask以及mask对应的终点u,那么由于路径长度是从小到大去遍历的,那么必然保证最终答案是最短路径,
- 假设用dfs做,那么dfs要考虑所有路径可能,然后去比较,那么会出现枚举的情况,然鹅bfs不会,因为省去所有长度比最短路径大的路径
- 2 总结一下:最关键的有两点
- 2.1 利用bfs能够将遍历路径的长度从小到大遍历的特性
- 2.2 使用<到达点,已经遍历的点,目前长度>来记录所有的遍历情况这个trick很聪明
// 这里看一个具体
[[1,2,3],[0],[0],[0]]
// 具体的遍历顺序为:
// 遍历到达的节点 目前遍历的节点 遍历路径的长度
0 00000001 0
1 00000010 0
2 00000100 0
3 00001000 0
1 00000011 1
2 00000101 1
3 00001001 1
0 00000011 1
0 00000101 1
0 00001001 1
2 00000111 2
3 00001011 2
1 00000111 2
3 00001101 2
1 00001011 2
2 00001101 2
0 00000111 3
0 00001011 3
0 00001101 3
3 00001111 4
3 00001111 4
class Solution {
public:
int shortestPathLength(vector<vector<int>>& graph) {
queue<tuple<int, int, int>> curLevel;
int n = graph.size();
vector<vector<bool>> seen(n, vector<bool>(1 << n));
for(int i = 0; i < n; ++i) {
curLevel.emplace(i, 1 << i, 0);
seen[i][1 << i] = true;
}
while(!curLevel.empty()) {
auto [node, mask, dist] = curLevel.front();
// printf("%d %x", node, mask);
// cout << dist << endl;
curLevel.pop();
// << not priority to -
if(mask == (1 << n) - 1) {
return dist;
}
for(auto neighbor : graph[node]) {
int newMask = mask | 1 << neighbor;
if(!seen[neighbor][newMask]) {
curLevel.emplace(neighbor, newMask, dist + 1);
seen[neighbor][newMask] = true;
}
}
}
return -1;
}
};
0864getAllKeys 获取所有钥匙的最短路径
1 题目
https://leetcode.cn/problems/shortest-path-to-get-all-keys/submissions/
2 解题思路
- 1 解题思路:
- 1.1 首先考虑使用bfs,因为从起点到终点,bfs的遍历深度就等于最短路径,那么有一个问题对于:[“@a.”,“bAB”]这样的地图如何知道经过a和b的最短路呢?
- 1.2 也就是这个bfs会走"回头路",但是又不是完全的回头路,因为走路的人的状态发生了变化,也就是手里多了钥匙,也就是bfs的变种:带状态(压缩)的bfs
- 1.3 那么就很容易想到,原来最普通的bfs判断是否走过就是只用了位置xy,那么现在我们多增加一个信息,也就是拥有的钥匙,那么该点没有走过变成了什么呢?那就是:该点位置没有走过,或者当前的拥有钥匙的状态在该点没有出现过
- 1.4 有了1.3我们就很容易知道,用什么数据结构去存顶点是否被访问啦: map<pair<int, int>, set> seenKey; 左边是该点的位置,右边是该点所经历过的所有钥匙的集合
- 1.5 还需要考虑如何记录路径长度:map<pair<pair<int, int>, int>, int> dis; 很显然,左侧是<xy,key>,右侧代表了xy在key情况下的路径长度
- 1.6 考虑一个具体[“@a.”,“bAB”]的例子即可:
cur: 0,0 -> 000000
next: 0,1 charis a-> 000001
next: 1,0 charis b-> 000010
cur: 0,1 -> 000001
next: 0,2 charis .-> 000001
next: 1,1 charis A-> 000001
next: 0,0 charis @-> 000001
cur: 1,0 -> 000010
next: 0,0 charis @-> 000010
cur: 0,2 -> 000001
cur: 1,1 -> 000001
next: 1,0 charis b-> 000011
class Solution {
public:
int m, n;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
int shortestPathAllKeys(vector<string>& grid) {
m = grid.size();
n = grid[0].size();
int i = 0;
int j = 0;
int keyNum = 0;
int stX = -1, stY = -1;
for(auto& s : grid) {
j = 0;
for(auto& c : s) {
if('a' <= c && c <= 'f') {
++keyNum;
}
if('@' == c) {
stX = i;
stY = j;
}
++j;
}
++i;
}
int tarKey = 0;
for(int i = 0; i < keyNum; ++i) {
tarKey = tarKey | (1 << i);
};
// cout << "tarKey is : " << bitset<8>(tarKey) << " | st: " << stX << " " << stY << endl;
return bfs(grid, stX, stY, tarKey);
}
bool canWalk(vector<string>& grid, int x, int y, int keyInfo) {
if('A' <= grid[x][y] && grid[x][y] <= 'F') {
int keyNum = grid[x][y] - 'A';
return ((keyInfo >> keyNum) & 1) != 0;
}
if('#' == grid[x][y]) {
return false;
}
return true;
}
int bfs(vector<string>& grid, int stX, int stY, int tarKey) {
queue<pair<pair<int, int>, int>> curLevel; // xy, key
curLevel.push({{stX, stY}, 0});
map<pair<pair<int, int>, int>, int> dis; // xy, key -> pathLen
map<pair<int, int>, set<int>> seenKey; // xy -> key
dis[{{stX, stY}, 0}] = 0;
seenKey[{stX, stY}] = {0};
int res = 0;
while(!curLevel.empty()) {
int curSize = curLevel.size();
while(curSize-- > 0) {
auto curNode = curLevel.front();
int curDis = dis[curNode];
int x = curNode.first.first;
int y = curNode.first.second;
int curKey = curNode.second;
curLevel.pop();
// cout << "cur: " << x << "," << y << " -> " << bitset<6>(curKey) << endl;
for(int mv = 0; mv < 4; ++mv) {
int nextX = x + dx[mv];
int nextY = y + dy[mv];
pair<int, int> nextNode = {nextX, nextY};
// nextNode not explored or has new keys
if(0 <= nextX && nextX < m && 0 <= nextY && nextY < n && \
canWalk(grid, nextX, nextY, curKey) && \
(0 == seenKey.count(nextNode) || 0 == seenKey[nextNode].count(curKey))) {
int nextKey = curKey;
if('a' <= grid[nextX][nextY] && grid[nextX][nextY] <= 'f') {
nextKey = curKey | (0x1 << (grid[nextX][nextY] - 'a'));
}
curLevel.push({{nextX, nextY}, nextKey});
seenKey[{nextX, nextY}].insert(nextKey);
dis[{nextNode, nextKey}] = dis[curNode] + 1;
// cout << "next: " << nextX << "," << nextY << " charis "<< grid[nextX][nextY] << "-> " << bitset<6>(nextKey) << endl;
if(nextKey == tarKey) {
return dis[curNode] + 1;
}
}
}
}
}
return -1;
}
};