- 847访问所有节点的最短路径
- 934最短桥(用到三色法,层级)
- 126. 单词接龙 II(dfs+bfs,必看)">126. 单词接龙 II(dfs+bfs,必看)
- 386. 字典序排数(巧妙)">386. 字典序排数(巧妙)
- 675. 为高尔夫比赛砍树(寻路算法,很重要)">675. 为高尔夫比赛砍树(寻路算法,很重要)
- 0-1BFS 最短路径问题(双端队列)
- 6081. 到达角落需要移除障碍物的最小数目(比较有细节)">6081. 到达角落需要移除障碍物的最小数目(比较有细节)
- 6081. 到达角落需要移除障碍物的最小数目(比较有细节)">6081. 到达角落需要移除障碍物的最小数目(比较有细节)
847访问所有节点的最短路径
使用三个量来标记 分别是当前的位置,经过的位置,走过的路径长度。这题还有一个要点,使用二进制数的形式来表示经过的路径
class Solution {public:int shortestPathLength(vector<vector<int>>& graph) {int n = graph.size();queue<tuple<int, int, int>> q;vector<vector<int>> seen(n, vector<int>(1 << n));for (int i = 0; i < n; ++i) {q.emplace(i, 1 << i, 0);//代表经过了第几位seen[i][1 << i] = true;}int ans = 0;while (!q.empty()) {auto [u, mask, dist] = q.front();q.pop();if (mask == (1 << n) - 1) {//全是1,代表经过了所有节点ans = dist;//此时路径长度确定break;}// 搜索相邻的节点for (int v: graph[u]) {// 将 mask 的第 v 位置为 1int mask_v = mask | (1 << v);if (!seen[v][mask_v]) {//减枝操作q.emplace(v, mask_v, dist + 1);seen[v][mask_v] = true;//经过的地方设为true;}}}return ans;}};//这里的主要的关于剪枝的想法,不只是处于相同的节点,还要经过的节点一样。放在题目中就是说v和mask一样。如果题目要求只看终点而不是经过所有的路径的话,这里凡是经过相同的节点都要剪枝。
934最短桥(用到三色法,层级)
非常标准的广度优先搜索的题目,找到最短的路径,因为是需要一层层的遍历的,所以需要先入先出的队列来进行遍历。深搜查找第一个岛屿的所有位置,然后使用bfs寻找第二个岛屿,并把过程经过的0赋值为2。
class Solution {public:vector<int> direction{-1, 0, 1, 0, -1};// 主函数int shortestBridge(vector<vector<int>>& grid) {int m = grid.size(), n = grid[0].size();queue<pair<int, int>> points;// dfs寻找第一个岛屿,并把1全部赋值为2bool flipped = false;for (int i = 0; i < m; ++i) {if (flipped) break;for (int j = 0; j < n; ++j) {if (grid[i][j] == 1) {dfs(points, grid, m, n, i, j);flipped = true;break;}}}// bfs寻找第二个岛屿,并把过程中经过的0赋值为2int x, y;int level = 0;while (!points.empty()){++level;int n_points = points.size();while (n_points--) {//这个设计很关键,能够满足一层层遍历的要求。auto [r, c] = points.front();points.pop();for (int k = 0; k < 4; ++k) {x = r + direction[k], y = c + direction[k+1];if (x >= 0 && y >= 0 && x < m && y < n) {if (grid[x][y] == 2) {continue;}if (grid[x][y] == 1) {return level;}points.push({x, y});grid[x][y] = 2;}}}}return 0;}// 辅函数void dfs(queue<pair<int, int>>& points, vector<vector<int>>& grid, int m, int n, int i, int j) {if (i < 0 || j < 0 || i == m || j == n || grid[i][j] == 2) {return;}if (grid[i][j] == 0) {points.push({i, j});//存进去的都是从第一个岛屿出发所能到达的第一层为0的部分。return;}grid[i][j] = 2;dfs(points, grid, m, n, i - 1, j);dfs(points, grid, m, n, i + 1, j);dfs(points, grid, m, n, i, j - 1);dfs(points, grid, m, n, i, j + 1);}};
126. 单词接龙 II(dfs+bfs,必看)
- 想要进行bfs,因为本题并没有图,所以第一步先建图
注意维护一个哈希表方便查找。
class Solution {public:vector<vector<string>> findLadders(string beginWord, string endWord, vector<string> &wordList) {vector<vector<string>> res;// 因为需要快速判断扩展出的单词是否在 wordList 里,因此需要将 wordList 存入哈希表,这里命名为「字典」unordered_set<string> dict = {wordList.begin(), wordList.end()};// 修改以后看一下,如果根本就不在 dict 里面,跳过if (dict.find(endWord) == dict.end()) {return res;}// 特殊用例处理dict.erase(beginWord);// 第 1 步:广度优先遍历建图// 记录扩展出的单词是在第几次扩展的时候得到的,key:单词,value:在广度优先遍历的第几层unordered_map<string, int> steps = {{beginWord, 0}};// 记录了单词是从哪些单词扩展而来,key:单词,value:单词列表,这些单词可以变换到 key ,它们是一对多关系unordered_map<string, set<string>> from = {{beginWord, {}}};int step = 0;bool found = false;queue<string> q = queue<string>{{beginWord}};int wordLen = beginWord.length();while (!q.empty()) {step++;int size = q.size();for (int i = 0; i < size; i++) {const string currWord = move(q.front());string nextWord = currWord;q.pop();// 将每一位替换成 26 个小写英文字母for (int j = 0; j < wordLen; ++j) {const char origin = nextWord[j];for (char c = 'a'; c <= 'z'; ++c) {nextWord[j] = c;if (steps[nextWord] == step) {//这里的意思是如果同一层也有相同的部分,就可以直接进入from里面,因为后面的dict.erase已经能够扩展出来的,防止后面又出现且距离更远。from[nextWord].insert(currWord);}if (dict.find(nextWord) == dict.end()) {continue;}// 如果从一个单词扩展出来的单词以前遍历过,距离一定更远,为了避免搜索到已经遍历到,且距离更远的单词,需要将它从 dict 中删除dict.erase(nextWord);// 这一层扩展出的单词进入队列q.push(nextWord);// 记录 nextWord 从 currWord 而来from[nextWord].insert(currWord);// 记录 nextWord 的 stepsteps[nextWord] = step;if (nextWord == endWord) {found = true;}}nextWord[j] = origin;}}if (found) {break;}}// 第 2 步:深度优先遍历找到所有解,从 endWord 恢复到 beginWord ,所以每次尝试操作 path 列表的头部if (found) {vector<string> Path = {endWord};dfs(res, endWord, from, Path);}return res;}void dfs(vector<vector<string>> &res, const string &Node, unordered_map<string, set<string>> &from,vector<string> &path) {if (from[Node].empty()) {//这是把里面所有路径都走一遍。res.push_back({path.rbegin(), path.rend()});return;}for (const string &Parent: from[Node]) {path.push_back(Parent);dfs(res, Parent, from, path);path.pop_back();}}};
386. 字典序排数(巧妙)
非常巧妙的使用广搜的方法来写。
class Solution {public:vector<int> lexicalOrder(int n) {vector<int> ret(n);int number = 1;for (int i = 0; i < n; i++) {ret[i] = number;if (number * 10 <= n) {//这里符合字典排序规则number *= 10;} else {while (number % 10 == 9 || number + 1 > n) {//这个相当于向前退一位number /= 10;}number++;}}return ret;}};
675. 为高尔夫比赛砍树(寻路算法,很重要)
题目限定了我们只能按照从低到高的顺序进行砍树,并且图中不存在高度相等的两棵树,这意味着整个砍树的顺序唯一确定,就是对所有树进行高度排序,就是完整的路线。
- 另外一个重要的性质是,点与点之间的最短路径,不会随着砍树过程的进程而发生变化,
综上所述,砍树的路线唯一确定,当我们求出每两个相邻的砍树点最短路径,并进行累加就是答案,求最短路径使用BFS比较好。
class Solution {public:int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};//四个方向//BFS求最短路径,即可int bfs(vector<vector<int>>& forest, int sx, int sy, int tx, int ty) {if (sx == tx && sy == ty) {//当起点和终点是同一个位置的时候直接返回0return 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;}int cutOffTree(vector<vector<int>>& forest) {vector<pair<int, int>> trees;int row = forest.size();int col = forest[0].size();for (int i = 0; i < row; ++i) {for (int j = 0; j < col; ++j) {if (forest[i][j] > 1) {trees.emplace_back(i, j);//把所有的树按照高度排序}}}sort(trees.begin(), trees.end(), [&](const pair<int, int> & a, const pair<int, int> & b) {return forest[a.first][a.second] < forest[b.first][b.second];});int cx = 0;int cy = 0;int ans = 0;for (int i = 0; i < trees.size(); ++i) {int steps = bfs(forest, cx, cy, trees[i].first, trees[i].second);//找两个点之间的最短距离if (steps == -1) {//其中任意两个点无法到达,就直接返回-1return -1;}ans += steps;cx = trees[i].first;cy = trees[i].second;}return ans;}};
0-1BFS 最短路径问题(双端队列)
参考链接
https://blog.csdn.net/Mr_dimple/article/details/1168640526081. 到达角落需要移除障碍物的最小数目(比较有细节)
从起点开始,加入队列。
- while队列非空,取队首元素,用这个节点更新其他节点。
- 如果可以更新的话:
- 1、边权为0,放到队首。(从边权为0的边走,不增加消耗,得多走这样的边)
- 2、边权为其他,放到队尾。(增加消耗,少用)
(这样,队列前面的元素值一定比后面的元素值小(可以手动模拟下),每次求队首元素来更新相连的点的距离,完美的替代了优先队列的功能!
const int d[4][2] = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};class Solution {public:int minimumObstacles(vector<vector<int>>& grid) {int n = grid.size(), m = grid[0].size();vector<vector<int>> dis(n, vector<int>(m, INT_MAX));dis[0][0] = 0;deque<pair<int, int>> dq;dq.emplace_back(0, 0);while (!dq.empty()) {auto [i, j] = dq.front();dq.pop_front();for (int k = 0; k < 4; ++k) {int ni = i + d[k][0], nj = j + d[k][1];if (ni < 0 || ni >= n || nj < 0 || nj >= m || dis[ni][nj] < INT_MAX)//这里就暗含了记忆化的问题,只要便利过就不要再次便利,这种矩阵最好都使用BFS来写。因为队列前面的元素值一定比后面的小continue;dis[ni][nj] = dis[i][j] + grid[ni][nj];if (grid[ni][nj])dq.emplace_back(ni, nj);elsedq.emplace_front(ni, nj);}}return dis[n - 1][m - 1];}};
