图的广度优先遍历
图的广度优先遍历
1162. 地图分析
#include <iostream> #include <vector> #include <queue> #include <algorithm> using namespace std; class Solution { public: int rows; int columns; // 上右下左 vector<int> move{-1, 0, 1, 0, -1}; bool isCoordinateLegal(int row, int column) { return row >= 0 && row < rows && column >= 0 && column < columns; } // 多源 DFS int maxDistance(vector<vector<int>> &grid) { rows = grid.size(); columns = grid[0].size(); // 访问标记数组 vector<vector<int>> visited(rows, vector<int>(columns, false)); // 海洋个数 int seas = 0; // 层级,由陆地向外扩展 int lever = 0; // 存放到陆地的曼哈顿距离为 lever 的格子 queue<pair<int, int>> q; for (int i = 0; i < rows; ++i) { for (int j = 0; j < columns; ++j) { if (grid[i][j] == 1) { q.push(make_pair(i, j)); visited[i][j] = true; } else { seas++; } } } // 全是海洋或陆地 if (seas == 0 || seas == rows * columns) return -1; while (!q.empty()) { int size = q.size(); // 同一层的一起出队 for (int i = 0; i < size; ++i) { auto cod = q.front(); q.pop(); // 四周没处理过的入队 for (int j = 0; j < 4; ++j) { int x = cod.first + move[j]; int y = cod.second + move[j + 1]; if (!isCoordinateLegal(x, y) || visited[x][y]) continue; q.push(make_pair(x, y)); visited[x][y] = true; } } lever++; } return lever - 1; } };
691. 贴纸拼词
#include <iostream> #include <vector> #include <queue> #include <algorithm> #include <unordered_set> using namespace std; class Solution { public: string getLeft(string target, string sticker) { string res = ""; int len1 = target.length(); int len2 = sticker.length(); int i = 0, j = 0; while (i < len1 && j < len2) { if (target[i] == sticker[j]) { i++; j++; } else if (target[i] < sticker[j]) { // sticker 消除不了 target[i] res.append(1, target[i]); i++; } else { j++; } } // 剩余的都追加到 res if (i < len1) res.append(target, i, len1 - i + 1); return res; } int minStickers(vector<string> &stickers, string target) { vector<vector<string>> graph(26); // 避免相同的字符串再次入队重复处理 unordered_set<string> visited; // 根据所有贴纸建图 for (int i = 0; i < stickers.size(); ++i) { string str = stickers[i]; // 贴纸里的字符排序 sort(str.begin(), str.end()); // u -> str: // 字符 u -> 能消去字符 u 的贴纸 str for (int j = 0; j < str.size(); ++j) { if (j == 0 || str[j] != str[j - 1]) graph[str[j] - 'a'].emplace_back(str); } } // 给定字符串里的字符排序 sort(target.begin(), target.end()); visited.emplace(target); // 层数就是要用掉的贴纸数 int level = 1; // 存储等待删除字符的字符串 queue<string> q; q.push(target); while (!q.empty()) { int len = q.size(); // 每次处理一层 for (int j = 0; j < len; ++j) { string str = q.front(); q.pop(); // 遍历所有能处理掉字符 str[0] 的字符串 for (const auto &sticker: graph[str[0] - 'a']) { // 获取剩余的字符串 string left = getLeft(str, sticker); if (left == "") { // 没有剩余的字符,说明目标字符串已经被消除光了 return level; } else if (visited.find(left) == visited.end()) { // 否则在之前没有加入队列的情况下,入队,等待使用其他贴纸进行消除 visited.emplace(left); q.push(left); } } } level++; } return -1; } };
O1 BFS
-
适用于图中所有的边的权值只有 0 和 1 两种值,求源点到目标点的最短距离
-
时间复杂度:O(节点数量 + 边的数量)
-
流程:
-
distance[i] 表示从源点到点 i 的最短距离,初始时所有点的 distance 设置为无穷大
-
源点进入双端队列,distance[源点] = 0
-
双端队列头部弹出 x,
A. 如果 x 为目标点,返回 distance[x] 表示源点到目标点的最短距离
B. 考察从 x 出发的每条边,假设某条边去 y 点,边权重为 w
a. 如果 distance[y] > distance[x] + w,更新 distance[y] = distance[x] + w;同时如果 w 为 0,y 从头部进入双端队列,为 1 就从尾部进入。重复步骤 3
b. 否则忽略该边,重复步骤 3
-
直到双端队列为空
-
2290. 到达角落需要移除障碍物的最小数目
#include <iostream> #include <vector> #include <queue> #include <algorithm> #include <unordered_set> using namespace std; class Solution { public: int rows, columns; vector<int> move{-1, 0, 1, 0, -1}; // 源点到目标点的距离 vector<vector<int>> distance; bool isCoordinateLegal(int row, int column) { return row >= 0 && row < rows && column >= 0 && column < columns; } int minimumObstacles(vector<vector<int>> &grid) { rows = grid.size(); columns = grid[0].size(); // 初始距离都为无穷大 distance.resize(rows, vector<int>(columns, INT_MAX)); // 存储点的坐标 deque<pair<int, int>> dq; dq.emplace_front(0, 0); // 源点到自身的距离为 0 distance[0][0] = 0; while (!dq.empty()) { // 每次都从头部弹出 auto point = dq.front(); dq.pop_front(); // 弹出的是目标点就返回最短距离 if (point.first == rows - 1 && point.second == columns - 1) return distance[rows - 1][columns - 1]; // 否则考察从 point 出发的每条边,假设某条边去点(x, y),边权重为 weight for (int i = 0; i < 4; ++i) { int x = point.first + move[i]; int y = point.second + move[i + 1]; // 下标越界或者到达点(x, y)的距离无法通过点 point 变得更短,就跳过 if (!isCoordinateLegal(x, y) || distance[x][y] <= distance[point.first][point.second] + grid[x][y]) continue; // 更新成更短 distance[x][y] = distance[point.first][point.second] + grid[x][y]; if (grid[x][y] == 0) // 边的权重为 0,放前面就能先出队列 dq.emplace_front(make_pair(x, y)); else dq.emplace_back(make_pair(x, y)); } } return -1; } };
1368. 使网格图至少有一条有效路径的最小代价
#include <iostream> #include <vector> #include <queue> #include <algorithm> #include <unordered_set> using namespace std; class Solution { public: int rows, columns; vector<vector<int>> move = {{}, {0, 1}, {0, -1}, {1, 0}, {-1, 0}}; vector<vector<int>> distance; bool isCoordinateLegal(int row, int column) { return row >= 0 && row < rows && column >= 0 && column < columns; } int minCost(vector<vector<int>> &grid) { rows = grid.size(); columns = grid[0].size(); distance.resize(rows, vector<int>(columns, INT_MAX)); deque<pair<int, int>> dq; dq.emplace_front(0, 0); distance[0][0] = 0; while (!dq.empty()) { auto point = dq.front(); dq.pop_front(); int x = point.first; int y = point.second; if (x == rows - 1 && y == columns - 1) return distance[rows - 1][columns - 1]; for (int i = 1; i <= 4; ++i) { int nx = x + move[i][0]; int ny = y + move[i][1]; // 箭头和要走的方向一致,代价就是 0 int cost = grid[x][y] == i ? 0 : 1; if (!isCoordinateLegal(nx, ny) || distance[nx][ny] <= distance[x][y] + cost) continue; distance[nx][ny] = distance[x][y] + cost; if (cost == 0) dq.emplace_front(nx, ny); else dq.emplace_back(nx, ny); } } return -1; } };
407. 接雨水 II
#include <iostream> #include <vector> #include <queue> #include <algorithm> #include <unordered_set> using namespace std; class Solution { public: struct cmp { bool operator()(vector<int> &a, vector<int> &b) { return a[2] > b[2]; } }; int rows, columns; vector<int> move{-1, 0, 1, 0, -1}; bool isCoordinateLegal(int row, int column) { return row >= 0 && row < rows && column >= 0 && column < columns; } int trapRainWater(vector<vector<int>> &heightMap) { rows = heightMap.size(); columns = heightMap[0].size(); // 标记是否已经放入堆中过 vector<vector<bool>> visited; // 按照单元格高度排序 priority_queue<vector<int>, vector<vector<int>>, cmp> heap; // 把矩阵四周放入堆,并且标记为访问过 visited.resize(rows, vector<bool>(columns, false)); for (int i = 0; i < rows; ++i) { for (int j = 0; j < columns; ++j) { if (i == 0 || i == rows - 1 || j == 0 || j == columns - 1) { visited[i][j] = true; heap.emplace(vector<int>{i, j, heightMap[i][j]}); } } } int res = 0; while (!heap.empty()) { auto cur = heap.top(); heap.pop(); int x = cur[0]; int y = cur[1]; int w = cur[2]; // w 只可能比 heightMap[x][y] 大,因为入堆的时候,选的就是较大值 res += w - heightMap[x][y]; for (int i = 0; i < 4; ++i) { int nx = x + move[i]; int ny = y + move[i + 1]; // 越界或者之前已经加入过堆中,就跳过 if (!isCoordinateLegal(nx, ny) || visited[nx][ny]) continue; // 标记访问过了 visited[nx][ny] = true; int nw = heightMap[nx][ny]; // 为后续的格子提供的高度为当前高度和之前高度的较大值 heap.emplace(vector<int>{nx, ny, max(w, nw)}); } } return res; } };
126. 单词接龙 II
#include <iostream> #include <vector> #include <queue> #include <unordered_set> #include <unordered_map> using namespace std; class Solution { public: unordered_set<string> dict; unordered_set<string> curLevel; // 为了去重,curLevel 中的多个字符串可能都能变成相同的字符串 unordered_set<string> nextLevel; // 反向图:str 可以由 vector 中的字符串变动一个字符得到 unordered_map<string, vector<string>> graph; // 结果 vector<vector<string>> res; // 记录路径,每生成一条路径,加入到结果 vector<string> path; void build(vector<string> &wordList) { for (const auto &item: wordList) dict.emplace(item); } // 一层层往下去建图,找到 target 时返回 true bool bfs(string wd, string target) { bool find = false; // 当前层加入 wd curLevel.emplace(wd); // 开始往下找由 wd 改变一个字符能得到的字符串 str,并将 str 放入 nextLevel while (!curLevel.empty()) { // 单词表删除当前层出现的单词,防止之后的某一层又出现相同的单词 for (const auto &item: curLevel) dict.erase(item); for (string word: curLevel) { // nw 的每个位置,字符从 a 换到 z for (int i = 0; i < word.length(); ++i) { // 新单词用于变换 string nw = word; for (char ch = 'a'; ch <= 'z'; ch++) { nw[i] = ch; // 检查是否在单词表中出现过(排除原来的自己 if (dict.find(nw) != dict.end() && nw != word) { // 找到目标单词 if (nw == target) find = true; // 建立反向图,表示可以由 word 变动一个字符得到 nw graph[nw].emplace_back(word); // 追加到下一层,并且去重 nextLevel.emplace(nw); } } } } if (find == true) return true; swap(curLevel, nextLevel); nextLevel.clear(); } return false; } void dfs(string wd, string target) { path.insert(begin(path), wd); if (wd == target) { res.emplace_back(path); } else if (graph.find(wd) != graph.end()) { // 倒过来往回找 for (const auto &nxt: graph[wd]) dfs(nxt, target); } // 回溯 path.erase(begin(path)); } vector<vector<string>> findLadders(string beginWord, string endWord, vector<string> &wordList) { build(wordList); if (dict.find(endWord) == dict.end()) return res; if (bfs(beginWord, endWord)) // 由 endWord 找生成 beginWord 的路径 dfs(endWord, beginWord); return res; } };
本文作者:n1ce2cv
本文链接:https://www.cnblogs.com/sprinining/p/18414706
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2021-09-14 行为型设计模式