图的广度优先遍历

图的广度优先遍历

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(节点数量 + 边的数量)

  • 流程:

    1. distance[i] 表示从源点到点 i 的最短距离,初始时所有点的 distance 设置为无穷大

    2. 源点进入双端队列,distance[源点] = 0

    3. 双端队列头部弹出 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

    4. 直到双端队列为空

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;
    }
};
posted @ 2024-09-14 21:22  n1ce2cv  阅读(5)  评论(0编辑  收藏  举报