• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

《数据机构与算法》 图论 专题训练:网格图

前言

该题单由灵茶山艾府提供,在此致谢每一位无私奉献的计算机爱好者。
题单链接:https://leetcode.cn/circle/discuss/YiXPXW/

梳理

重点

  1. DFS
  2. BFS
  3. 并查集

难点

  1. 多源BFS
  2. 带权并查集
  3. 综合其它算法的应用

问题

  1. 如何思考带限制条件的网格图的搜索方案?

若是要在一个范围内寻找一个最优解,可以考虑图上二分查找或三分查找(选择二分还是三分查找取决于二元方程式是一次还是二次方程);若是涉及连通性,可以考虑(排序后)使用并查集;若是涉及一块连通分量的值有关的,可以简单考虑DFS/BFS;若是涉及多块连通分量的值,则可以考虑带权并查集;如果限定了某些方向,满足状态转移的情况下可以考虑使用动态规划。

  1. 对于网格图的搜索,如何选择恰当的算法(BFS或DFS)进行解决?

对于只需要寻找一个可行方案的情况,通常考虑使用DFS;对于需要在多个可行方案中进行选取的情况,通常考虑使用BFS。

  1. 当朴素的搜索算法无法满足要求时,如何思考进一步的优化?

DFS和BFS本质来讲,其实就是暴力,对于暴力算法来说,时间和空间的开销都是比较大的。那么,首先可以考虑进行状态压缩,这可以减小空间开销;其次要学会剪枝,在较大的网格图中进行BFS,也可以优化为双向BFS,这可以减小时间开销;最后,若是带有一些特别的限制条件,可以考虑综合使用其它算法。

  1. 在边长均相等的图中,为什么BFS可以寻找出一条最短路径,对访问过的节点进行标记是可行的呢?

因为在访问到一个节点时,若该节点已被标记为访问过,则说明一定存在一条更短的路径可以抵达当前的节点。

网格图DFS

LeetCode200 岛屿数量
class Solution {
private:
    int dx[4] = {-1, 1, 0, 0};
    int dy[4] = {0, 0, -1, 1};
    int row, column;

public:
    inline bool check(int &r, int &c, vector<vector<char>> &grid) {
        return r >= 0 && r < row && c >= 0 && c < column && grid[r][c] == '1';
    }

    void dfs(vector<vector<char>> &grid, int r, int c) {
        grid[r][c] = '0';
        for (int i = 0; i < 4; ++ i) {
            int _r = r + dx[i], _c = c + dy[i];
            if (check(_r, _c, grid)) {
                dfs(grid, _r, _c);
            }
        }
    }

    int numIslands(vector<vector<char>>& grid) {
        int ans = 0;
        row = grid.size(), column = grid[0].size();
        for (int i = 0; i < row; ++ i) {
            for (int j = 0; j < column; ++ j) {
                if (grid[i][j] == '1') {
                    ++ ans;
                    dfs(grid, i, j);
                }
            }
        }
        return ans;
    }
};
LeetCode695 岛屿的最大面积
class Solution {
private:
    int x[4] = {-1, 1, 0, 0}, y[4] = {0, 0, -1, 1};
    int row, column;

public:
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        int ans = 0;
        row = grid.size(), column = grid[0].size();
        for (int i = 0; i < row; ++ i) {
            for (int j = 0; j < column; ++ j) {
                if (grid[i][j]) {
                    ans = max(ans, dfs(grid, i, j));
                }
            }
        }
        return ans;
    }

    inline bool check(int &r, int &c, vector<vector<int>>& grid) {
        return r >= 0 && r < row && c >= 0 && c < column && grid[r][c];
    }

    int dfs(vector<vector<int>>& grid, int r, int c) {
        int res = 1;
        grid[r][c] = 0;
        for (int i = 0; i < 4; ++ i) {
            int _r = r + x[i], _c = c + y[i];
            if (check(_r, _c, grid)) {
                res += dfs(grid, _r, _c);
            }
        }
        return res;
    }
};
面试题16.19 水域大小
class Solution {
private:
    int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
    int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};
    int row, column;

public:
    vector<int> pondSizes(vector<vector<int>>& land) {
        vector<int> ans;
        row = land.size(), column = land[0].size();
        for (int i = 0; i < row; ++ i) {
            for (int j = 0; j < column; ++ j) {
                if (!land[i][j]) {
                    ans.emplace_back(dfs(land, i, j));
                }
            }
        }
        sort(ans.begin(), ans.end());
        return ans;
    }

    int dfs(vector<vector<int>>& land, int r, int c) {
        int res = land[r][c] = 1;
        for (int i = 0; i < 8; ++ i) {
            int _r = r + dx[i], _c = c + dy[i];
            if (_r >= 0 && _c >= 0 && _r < row && _c < column && !land[_r][_c]) {
                res += dfs(land, _r, _c);
            }
        }
        return res;
    }
};
LeetCode463 岛屿的周长
int islandPerimeter(int** grid, int gridSize, int* gridColSize){
    int i, j;
    int sum = 0;
    for (i = 0; i < gridSize; i ++) {
        for (j = 0; j < *gridColSize; j ++) {
            if (grid[i][j] == 1) {
                sum += 4;
                // a[i][j] = 0;
                if (i > 0 && grid[i - 1][j] == 1) {
                    sum -= 1;
                }
                if (i <= gridSize - 2 && grid[i + 1][j] == 1) {
                    sum -= 1;
                }
                if (j > 0 && grid[i][j - 1] == 1) {
                    sum -= 1;
                }
                if (j <= *gridColSize - 2 && grid[i][j + 1] == 1) {
                    sum -= 1;
                }
            }
        }
    }
    return sum;
}
LeetCode2658 网格图中鱼的最大数目
class Solution {
private:
    int x[4] = {-1, 1, 0, 0}, y[4] = {0, 0, -1, 1};
    int row, column;

public:
    int findMaxFish(vector<vector<int>>& grid) {
        int ans = 0;
        row = grid.size(), column = grid[0].size();
        for (int i = 0; i < row; ++ i) {
            for (int j = 0; j < column; ++ j) {
                if (grid[i][j]) {
                    ans = max(ans, dfs(grid, i, j));
                }
            }
        }
        return ans;
    }

    inline bool check(int &r, int &c, vector<vector<int>>& grid) {
        return r >= 0 && r < row && c >= 0 && c < column && grid[r][c];
    }

    int dfs(vector<vector<int>>& grid, int r, int c) {
        int res = grid[r][c];
        grid[r][c] = 0;
        for (int i = 0; i < 4; ++ i) {
            int _r = r + x[i], _c = c + y[i];
            if (check(_r, _c, grid)) {
                res += dfs(grid, _r, _c);
            }
        }
        return res;
    }
};
LeetCode1034 边界着色
class Solution {
private:
    int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
    int R, C;

public:
    vector<vector<int>> colorBorder(vector<vector<int>>& grid, int row, int col, int color) {
        R = grid.size(), C = grid[0].size();
        vector<vector<bool>> vis(R, vector<bool>(C, true));
        dfs(grid, vis, row, col, color);
        return grid;
    }

    void dfs(vector<vector<int>>& grid, vector<vector<bool>> &vis, int row, int col, int color) {
        bool need = !row || !col || row == R - 1 || col == C - 1;
        vis[row][col] = false;
        for (int i = 0; i < 4; ++ i) {
            int r = row + dx[i], c = col + dy[i];
            if (r >= 0 && r < R && c >= 0 && c < C && vis[r][c]) {
                if (grid[r][c] == grid[row][col]) {
                    dfs(grid, vis, r, c, color);
                } else {
                    need = true;
                }
            }
        }
        if (need) {
            grid[row][col] = color;
        }
    }
};
LeetCode1020 飞地的数量
class Solution {
private:
    int dx[4] = {-1, 1, 0, 0};
    int dy[4] = {0, 0, -1, 1};
    int row, column;

public:
    int numEnclaves(vector<vector<int>>& grid) {
        int ans = 0;
        row = grid.size(), column = grid[0].size();
        for (int i = 0; i < row; ++ i) {
            if (grid[i][0]) {
                dfs(grid, i, 0);
            }
            if (grid[i][column - 1]) {
                dfs(grid, i, column - 1);
            }
        }
        for (int i = 0; i < column; ++ i) {
            if (grid[0][i]) {
                dfs(grid, 0, i);
            }
            if (grid[row - 1][i]) {
                dfs(grid, row - 1, i);
            }
        }
        for (int i = 1; i < row - 1; ++ i) {
            for (int j = 1; j < column - 1; ++ j) {
                if (grid[i][j]) {
                    ++ ans;
                }
            }
        }
        return ans;
    }

    void dfs(vector<vector<int>> &grid, int r, int c) {
        grid[r][c] = 0;
        for (int i = 0; i < 4; ++ i) {
            int _r = r + dx[i], _c = c + dy[i];
            if (_r >= 0 && _r < row && _c >= 0 && _c < column && grid[_r][_c]) {
                dfs(grid, _r, _c);
            }
        }
    }
};
LeetCode1254 统计封闭岛屿的数目
class Solution {
private:
    int dx[4] = {-1, 1, 0, 0};
    int dy[4] = {0, 0, -1, 1};
    int row, column;

public:
    int closedIsland(vector<vector<int>>& grid) {
        int ans = 0;
        row = grid.size(), column = grid[0].size();
        for (int i = 0; i < row; ++ i) {
            for (int j = 0; j < column; ++ j) {
                if (grid[i][j] == 0 && dfs(grid, i, j)) {
                    ++ ans;
                }
            }
        }
        return ans;
    }

    bool check(int r, int c) {
        return r >= 0 && r < row && c >= 0 && c < column;
    }

    bool dfs(vector<vector<int>>& grid, int r, int c) {
        bool res = r != 0 && c != 0 && r != (row - 1) && c != (column - 1);
        grid[r][c] = 1;
        for (int i = 0; i < 4; ++ i) {
            int new_r = r + dx[i], new_c = c + dy[i];
            if (check(new_r, new_c) && !grid[new_r][new_c]) {
                bool b = dfs(grid, new_r, new_c);
                res = res && b;
            }
        }
        return res;
    }
};
LeetCode130 被围绕的区域
class Solution {
private:
    int dx[4] = {-1, 1, 0, 0};
    int dy[4] = {0, 0, -1, 1};
    int row, column;

public:
    void solve(vector<vector<char>>& board) {
        row = board.size(), column = board[0].size();
        vector<vector<bool>> flag(row, vector<bool>(column, true));
        for (int i = 0; i < row; ++ i) {
            if (board[i][0] == 'O' && flag[i][0]) dfs(board, flag, i, 0);
            if (board[i][column - 1] == 'O' && flag[i][column - 1]) dfs(board, flag, i, column - 1);
        }
        for (int i = 1; i < column - 1; ++ i) {
            if (board[0][i] == 'O' && flag[0][i]) dfs(board, flag, 0, i);
            if (board[row - 1][i] == 'O' && flag[row - 1][i]) dfs(board, flag, row - 1, i);
        }
        for (int i = 1; i < row - 1; ++ i) {
            for (int j = 1; j < column - 1; ++ j) {
                if (flag[i][j]) board[i][j] = 'X';
            }
        }
    }

    void dfs(vector<vector<char>>& board, vector<vector<bool>>& flag, int r, int c) {
        flag[r][c] = false;
        for (int i = 0; i < 4; ++ i) {
            int _r = r + dx[i], _c = c + dy[i];
            if (_r >= 0 && _r < row && _c >= 0 && _c < column && board[_r][_c] == 'O' && flag[_r][_c]) {
                dfs(board, flag, _r, _c);
            }
        }
    }
};
LeetCode1391 检查网格中是否存在有效路径
class Solution {
    //垃圾代码...建议去看评论区大佬的代码...
private:
    constexpr static int DIR[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    int m, n;
    
public:
    int getDirection(int dir, int streetType) {
        switch(dir) {
            case 0://上
                switch(streetType) {
                    case 1:
                    case 5:
                    case 6:
                        dir = -1;
                        break;
                    case 3:
                        dir = 2;
                        break;
                    case 4:
                        dir = 3;
                        break;
                }
                break;
            case 1://下
                switch(streetType) {
                    case 1:
                    case 3:
                    case 4:
                        dir = -1;
                        break;
                    case 5:
                        dir = 2;
                        break;
                    case 6:
                        dir = 3;
                        break;
                }
                break;
            case 2://左
                switch(streetType) {
                    case 2:
                    case 3:
                    case 5:
                        dir = -1;
                        break;
                    case 4:
                        dir = 1;
                        break;
                    case 6:
                        dir = 0;
                        break;
                }
                break;
            case 3://右
                switch(streetType) {
                    case 2:
                    case 4:
                    case 6:
                        dir = -1;
                        break;
                    case 3:
                        dir = 1;
                        break;
                    case 5:
                        dir = 0;
                        break;
                }
                break;
        }
        return dir;
    }

    void dfs(vector<vector<int>>& grid, int r, int c, int dir, bool &ans) {
        //dir:上0 下1 左2 右3
        if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == -1) return ;
        int nextDir = getDirection(dir, grid[r][c]);
        if (nextDir == -1) return ;
        int val = grid[r][c];
        grid[r][c] = -1;
        if (r == m - 1 && c == n - 1) {
            ans = true;
            return ;
        }
        dfs(grid, r + DIR[nextDir][0], c + DIR[nextDir][1], nextDir, ans);
        grid[r][c] = val;
    }

    bool hasValidPath(vector<vector<int>>& grid) {
        m = grid.size(), n = grid[0].size();
        bool ans = m == 1 && n == 1;
        switch (grid[0][0]) {
            case 1:
            case 6:
                dfs(grid, 0, 1, 3, ans);
                break;
            case 2:
            case 3:
                dfs(grid, 1, 0, 1, ans);
                break;
            case 4:
                dfs(grid, 1, 0, 1, ans);
                if (!ans) dfs(grid, 0, 1, 3, ans);
        }
        return ans;
    }
};
LeetCode417 太平洋大西洋水流问题
class Solution {
private:
    constexpr static int DIR[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    constexpr static int TARGET = 3;//1 | 2

public:
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        int n = heights.size(), m = heights[0].size();
        vector<vector<int>> vis(n, vector<int>(m, 0));
        vector<vector<int>> ans;
        vector<int> r, c;
        auto bfs = [&](int x) {
            int front = 0, rear, tail;
            while (front < (rear = r.size())) {
                tail = rear;
                while (front < tail) {
                    int row = r[front], column = c[front];
                    if (vis[row][column] == TARGET) {
                        vector<int> v = {row, column};
                        ans.emplace_back(v);
                    }
                    ++ front;
                    for (int i = 0; i < 4; ++ i) {
                        int nr = DIR[i][0] + row, nc = DIR[i][1] + column;
                        if (nr >= 0 && nr < n && nc >= 0 && nc < m && vis[nr][nc] < x && heights[nr][nc] >= heights[row][column]) {
                            r.emplace_back(nr), c.emplace_back(nc);
                            vis[nr][nc] |= x;
                        }
                    }
                }
            }
        };
        for (int i = 0; i < m; ++ i) {
            r.emplace_back(0), c.emplace_back(i);
            vis[0][i] = 1;
        }
        for (int i = 1; i < n; ++ i) {
            r.emplace_back(i), c.emplace_back(0);
            vis[i][0] = 1;
        }
        bfs(1);
        r.clear(), c.clear();
        for (int i = 0; i < n; ++ i) {
            r.emplace_back(i), c.emplace_back(m - 1);
            vis[i][m - 1] |= 2;
        }
        for (int i = 0; i < m - 1; ++ i) {
            r.emplace_back(n - 1), c.emplace_back(i);
            vis[n - 1][i] |= 2;
        }
        bfs(2);
        return ans;
    }
};
LeetCode529 扫雷游戏
class Solution {
private:
    constexpr static int DIR[8][2] = {{-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}};

public:
    vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {
        int row = board.size(), column = board[0].size();
        for (int i = 0; i < click.size(); i += 2) {
            int x = click[i], y = click[i + 1];
            if (board[x][y] == 'M') {
                board[x][y] = 'X';
            } else if (board[x][y] == 'E') {
                board[x][y] = 'B';
                vector<int> nextClick;
                for (int j = 0; j < 8; ++ j) {
                    int nex = x + DIR[j][0], ney = y + DIR[j][1];
                    if (nex >= 0 && nex < row && ney >= 0 && ney < column) {
                        if (board[nex][ney] == 'M' || board[nex][ney] == 'X') {
                            if (board[x][y] == 'B') {
                                board[x][y] = '1';
                            } else {
                                board[x][y] += 1;
                            }
                        } else {
                            nextClick.emplace_back(nex), nextClick.emplace_back(ney);
                        }
                    }
                }
                if (!isdigit(board[x][y])) {
                    click.insert(click.end(), nextClick.begin(), nextClick.end());
                }
            } 
        }
        return board;
    }
};
LeetCode1559 二维网格图中探测环
class Solution {
private:
    int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
    int row, column;
    char ch;

public:
    void dfs(vector<vector<char>>& grid, vector<vector<bool>> &v, int x, int y, int len, bool &flag) {
        v[x][y] = true;
        int cnt = 0;
        for (int i = 0; i < 4; ++ i) {
            int _x = x + dx[i], _y = y + dy[i];
            if (_x >= 0 && _x < row && _y >= 0 && _y < column && grid[_x][_y] == ch) {
                if (len >= 3 && v[_x][_y] && ++ cnt >= 2) {
                    flag = true;
                    return ;
                }
                if (!v[_x][_y]) dfs(grid, v, _x, _y, len + 1, flag);
                if (flag) return ;
            }
        }
    }

    bool containsCycle(vector<vector<char>>& grid) {
        bool ans = false;
        row = grid.size(), column = grid[0].size();       
        vector<vector<bool>> vis(row, vector<bool>(column));
        for (int i = 0; i < row; ++ i) {
            for (int j = 0; j < column; ++ j) {
                if (!vis[i][j]) {
                    ch = grid[i][j];
                    dfs(grid, vis, i, j, 1, ans);
                    if (ans) return true;
                }
            }
        }
        return false;
    }
};
LeetCode827 最大人工岛
class Solution {
private:
    int dx[4] = {-1, 1, 0, 0};
    int dy[4] = {0, 0, -1, 1};
    int row, column;
    vector<int> bcj, sz;

public:
    int find(int x) {
        if (x != bcj[x]) return bcj[x] = find(bcj[x]);
        return x;
    }

    void merge(int x, int y) {
        x = find(x), y = find(y);
        if (x == y) return ;
        sz[x] += sz[y];
        bcj[y] = x;
    }

    int largestIsland(vector<vector<int>>& grid) {
        auto get = [&](int i, int j) -> int {
            int res = 1;
            unordered_set<int> st;
            for (int k = 0; k < 4; ++ k) {
                int r = i + dx[k], c = j + dy[k];
                if (check(r, c)) {
                    int key = row * r + c;
                    int val = find(key);
                    if (st.find(val) == st.end()) {
                        res += sz[val];
                        st.insert(val);
                    }
                }
            }
            return res;
        };
        int ans = 0;
        row = grid.size(), column = grid[0].size();
        bcj = vector<int>(row * column);
        sz = vector<int>(row * column);
        for (int i = 0; i < row; ++ i) {
            for (int j = 0; j < column; ++ j) {
                int k = i * row + j;
                bcj[k] = k;
                sz[k] = grid[i][j];
            }
        }
        for (int i = 0; i < row; ++ i) {
            for (int j = 0; j < column; ++ j) {
                if (grid[i][j]) {
                    dfs(grid, i, j);
                    ans = max(ans, sz[i * row + j]);
                }
            }
        }
        for (int i = 0; i < row; ++ i) {
            for (int j = 0; j < column; ++ j) {
                if (!grid[i][j]) {
                    ans = max(ans, get(i, j));
                }
            }
        }
        return ans;
    }

    bool check(int r, int c) {
        return r >= 0 && r < row && c >= 0 && c < column;
    }

    void dfs(vector<vector<int>> &grid, int r, int c) {
        grid[r][c] = 0;
        int p = r * row + c;
        for (int i = 0; i < 4; ++ i) {
            int _r = r + dx[i], _c = c + dy[i];
            if (check(_r, _c) && grid[_r][_c] == 1) {
                merge(_r * row + _c, p);
                dfs(grid, _r, _c);
            }
        }
        grid[r][c] = 2;
    }
};

网格图BFS

LeetCode521 01矩阵
class Solution {
private:
    int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};

public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        int r = mat.size(), c = mat[0].size(), front = 0, rear, tail, dep = 1;
        vector<vector<bool>> vis(r, vector<bool>(c));
        vector<pair<int, int>> vec;//存储访问过的节点(i, j),本质为队列
        for (int i = 0; i < r; ++ i) {
            for (int j = 0; j < c; ++ j) {
                if (!mat[i][j]) {//将所有0存储到vec,用于多源BFS
                    vec.emplace_back(make_pair(i, j));//存储节点
                    vis[i][j] = true;//标记为已访问
                }
            }
        }
        rear = vec.size();
        while (front < rear) {//多源BFS
            tail = rear;
            while (front < tail) {
                int x = vec[front].first, y = vec[front].second;
                for (int i = 0; i < 4; ++ i) {
                    int _x = x + dx[i], _y = y + dy[i];
                    if (_x < 0 || _x >= r || _y < 0 || _y >= c) continue;
                    if (vis[_x][_y] == false) {//尚未访问过
                        mat[_x][_y] = dep;
                        vis[_x][_y] = true;
                        vec.emplace_back(make_pair(_x, _y));
                    }
                }
                ++ front;
            }
            ++ dep;//距离加1
            rear = vec.size();
        }
        return mat;
    }
};
LeetCode994 糜烂的橘子
class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
        int m = grid.size(), n = grid[0].size(), ans = 0, cnt = 0;
        vector<int> row, column;
        for (int i = 0; i < m; ++ i) {
            for (int j = 0; j < n; ++ j) {
                if (grid[i][j]) ++ cnt;
                if (grid[i][j] == 2) row.emplace_back(i), column.emplace_back(j);
            }
        }
        for (int front = 0, rear = row.size(), tail; front < rear; rear = row.size()) {
            tail = rear;
            while (front < tail) {
                int r = row[front], c = column[front];
                ++ front;
                for (int i = 0; i < 4; ++ i) {
                    int x = r + dx[i], y = c + dy[i];
                    if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == 1) {
                        grid[x][y] = 2;
                        row.emplace_back(x), column.emplace_back(y);
                    }
                }
            }
            if (tail < row.size()) ++ ans;
        }
        if (cnt != row.size()) return -1;
        return ans;
    }
};
LeetCode2684 矩阵中移动的最大次数
class Solution {
private:
    int row, column;
public:
    /*
    假设从(r, c)可以走到(r, c + 1)
    也可以从(r + 1, c + 1)走到(r, c + 1)
    那么我们只需要走一次(r, c + 1)即可
    为什么?
    因为从(r, c + 1)能走到的最远距离,必定是相同的
    因此同一个右侧结点,最多只需要走一次即可
    */
    void dfs(int r, int c, int len, int &mx, vector<vector<int>> &grid) {
        if (len > mx) mx = len;
        if (c + 1 == column) return ;
        bool flag = false;
        for (int i = -1; i <= 1; ++ i) {
            if (r + i >= 0 && r + i < row && grid[r][c] < grid[r + i][c + 1]) {
                dfs(r + i, c + 1, len + 1, mx, grid);
                flag = true;
            }
        }
        if (flag) grid[r][c] = 0;
    }

    int maxMoves(vector<vector<int>>& grid) {
        row = grid.size(), column = grid[0].size();
        int ans = 0;
        for (int i = 0; i < row; ++ i) {
            dfs(i, 0, 0, ans, grid);
        }
        return ans;
    }
};
LeetCode1926 迷宫中离入口最近的出口
class Solution {
private:
    int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, 1, -1};

public:
    int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
        int m = maze.size(), n = maze[0].size(), mi = 0;
        vector<int> r, c;
        r.emplace_back(entrance[0]), c.emplace_back(entrance[1]);
        maze[entrance[0]][entrance[1]] = '+';
        int front = 0, rear = 1, tail;
        while (front < rear) {
            tail = rear;
            ++ mi;
            while (front < tail) {
                
                for (int i = 0; i < 4; ++ i) {
                    int x = r[front] + dx[i], y = c[front] + dy[i];
                    if (x < m && x >= 0 && y >= 0 && y < n && maze[x][y] != '+') {
                        maze[x][y] = '+';
                        if (x == 0 || x == m - 1 || y == 0 || y == n - 1) {
                            return mi;
                        }
                        r.emplace_back(x), c.emplace_back(y);
                    }
                }
                ++ front;
            }
            rear = r.size();
        }
        return -1;
    }
};
LeetCode1162 地图分析
class Solution {
public:
    int maxDistance(vector<vector<int>>& grid) {
        int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
        int n = grid.size();
        int ans = 0;
        vector<int> r, c;
        for (int i = 0; i < n; ++ i) {
            for (int j = 0; j < n; ++ j) {
                if (grid[i][j]) {
                    r.emplace_back(i), c.emplace_back(j);
                }
            }
        }
        if (r.size() == 0 || r.size() == n * n) {
            return -1;
        }
        int front = 0, rear = r.size(), tail;
        while (front < rear) {
            tail = rear;
            while (front < tail) {
                for (int i = 0; i < 4; ++ i) {
                    int x = r[front] + dx[i], y = c[front] + dy[i];
                    if (x >= 0 && x < n && y >= 0 && y < n && grid[x][y] == 0) {
                        grid[x][y] = 1;
                        r.emplace_back(x), c.emplace_back(y);
                    }
                }
                ++ front;
            }
            rear = r.size();
            if (tail < rear) ++ ans;
        }
        return ans;
    }
};
LeetCode934 最短的桥
class Solution {
/*
    先dfs找出一个岛的四周的全部的0
    访问过程中,用2标记已经访问过的结点
    然后从这些0进行多源BFS
    BFS遇到1,就是遇到另一个岛屿
*/
private:
    int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
    int n;
    vector<pair<int, int>> vec;

public:
    void dfs(vector<vector<int>>& grid, int r, int c) {
        for (int i = 0; i < 4; ++ i) {
            int x = r + dx[i], y = c + dy[i];
            if (x >= 0 && x < n && y >= 0 && y < n) {
                if (grid[x][y] == 1) {
                    grid[x][y] = 2;
                    dfs(grid, x, y);
                } else if (grid[x][y] == 0) {
                    grid[x][y] = 2;
                    vec.emplace_back(make_pair(x, y));
                }
            }
        }
    }

    int shortestBridge(vector<vector<int>>& grid) {
        n = grid.size();
        int ans = 1;
        bool flag = false;
        for (int i = 0; i < n; ++ i) {
            for (int j = 0; j < n; ++ j) {
                if (grid[i][j] == 1) {
                    grid[i][j] = 2;
                    dfs(grid, i, j);
                    flag = true;
                    break;
                }
            }
            if (flag) {
                break;
            }
        }
        int fro = 0, rear = vec.size(), tail;
        while (fro < rear) {
            tail = rear;
            while (fro < tail) {
                for (int i = 0; i < 4; ++ i) {
                    int x = vec[fro].first + dx[i], y = vec[fro].second + dy[i];
                    if (x >= 0 && x < n && y >= 0 && y < n) {
                        if (grid[x][y] == 1) {
                            return ans;
                        } else if (!grid[x][y]) {
                            vec.emplace_back(make_pair(x, y));
                            grid[x][y] = 2;
                        }
                    }
                }
                fro ++;
            }
            rear = vec.size();
            if (tail < rear) ++ ans;
        }
        return -1;
    }
};
LeetCode2146 价格范围内最高排名的k样物品
class Solution {
public:
    typedef tuple<int, int, int> TP;
    constexpr static int DIR[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    vector<vector<int>> highestRankedKItems(vector<vector<int>>& grid, vector<int>& pricing, vector<int>& start, int k) {
        vector<vector<int>> ans;
        priority_queue<TP, vector<TP>, greater<TP>> pq;
        int front = 0, rear, tail, m = grid.size(), n = grid[0].size(), low = pricing[0], high = pricing[1];
        vector<int> r, c;
        r.emplace_back(start[0]), c.emplace_back(start[1]);
        if (grid[start[0]][start[1]] > 1) {
            if (grid[start[0]][start[1]] >= low && grid[start[0]][start[1]] <= high) {
                vector<int> res = {start[0], start[1]};
                ans.emplace_back(res);
                -- k;
            }
            grid[start[0]][start[1]] = 0;
        }
        auto check = [&](int row, int col) -> bool {
            if (row < 0 || row >= m || col < 0 || col >= n || !grid[row][col]) {
                return false;
            }
            return true;
        };
        while (front < (rear = r.size())) {
            while (!pq.empty() && k -- > 0) {
                vector<int> res;
                TP tp = pq.top();
                pq.pop();
                res.emplace_back(get<1>(tp)), res.emplace_back(get<2>(tp));
                ans.emplace_back(res);
            }
            if (k <= 0) break;
            int tail = rear;
            while (front < tail) {
                int row = r[front], col = c[front ++];
                for (int i = 0; i < 4; ++ i) {
                    int ner = row + DIR[i][0], nec = col + DIR[i][1];
                    if (check(ner, nec)) {
                        r.emplace_back(ner), c.emplace_back(nec);
                        if (grid[ner][nec] >= low && grid[ner][nec] <= high) {
                            pq.push(TP(grid[ner][nec], ner, nec));
                        }
                        grid[ner][nec] = 0;
                    }
                }
            }
        }
        return ans;
    }
};
LeetCode1293 网格中的最短路径
struct Nagato {
    int x, y;
    int rest;
    Nagato(int _x, int _y, int _r): x(_x), y(_y), rest(_r) {}
};

class Solution {
private:
    static constexpr int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

public:
    int shortestPath(vector<vector<int>>& grid, int k) {
        int m = grid.size(), n = grid[0].size();
        if (m == 1 && n == 1) {
            return 0;
        }
        if (k >= m + n - 3) {
            return m + n - 2;
        }
        k = min(k, m + n - 3);
        bool visited[m][n][k + 1];
        memset(visited, false, sizeof(visited));
        queue<Nagato> q;
        q.emplace(0, 0, k);
        visited[0][0][k] = true;

        for (int step = 1; q.size() > 0; ++step) {
            int cnt = q.size();
            for (int _ = 0; _ < cnt; ++_) {
                Nagato cur = q.front();
                q.pop();
                for (int i = 0; i < 4; ++i) {
                    int nx = cur.x + dirs[i][0];
                    int ny = cur.y + dirs[i][1];
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                        if (grid[nx][ny] == 0 && !visited[nx][ny][cur.rest]) {
                            if (nx == m - 1 && ny == n - 1) {
                                return step;
                            }
                            q.emplace(nx, ny, cur.rest);
                            visited[nx][ny][cur.rest] = true;
                        }
                        else if (grid[nx][ny] == 1 && cur.rest > 0 && !visited[nx][ny][cur.rest - 1]) {
                            q.emplace(nx, ny, cur.rest - 1);
                            visited[nx][ny][cur.rest - 1] = true;
                        }
                    }
                }
            }
        }
        return -1;
    }
};
LeetCode1210 穿过迷宫的最少移动次数
//这道题灵茶山艾府写得非常巧妙,值得直接学习他的写法与思路
//灵茶山艾府题解链接:https://leetcode.cn/problems/minimum-moves-to-reach-target-with-rotations/solutions/2093126/huan-zai-if-elseyi-ge-xun-huan-chu-li-li-tw8b/

综合应用

LeetCode1631 最小体力消耗路径
class Solution {
public:
    int minimumEffortPath(vector<vector<int>>& heights) {
        int row = heights.size(), column = heights[0].size(), left = 0, right = 1e6, middle;
        if (row == 1 && column == 1) return 0;//已经位于终点
        int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
        auto check = [&](int middle) -> bool {
            vector<vector<bool>> flag(row, vector<bool>(column));
            vector<int> r, c;
            r.emplace_back(0), c.emplace_back(0);
            flag[0][0] = true;
            int front = 0, rear = 1, tail;
            while (front < rear) {
                tail = rear;
                while (front < tail) {
                    for (int i = 0; i < 4; ++ i) {
                        int x = dx[i] + r[front], y = dy[i] + c[front];
                        if (x >= 0 && x < row && y >= 0 && y < column && !flag[x][y] && abs(heights[x][y] - heights[r[front]][c[front]]) <= middle) {
                            if (x == row - 1 && y == column - 1) return true;
                            r.emplace_back(x), c.emplace_back(y);
                            flag[x][y] = true;
                        } 
                    }
                    ++ front;
                }
                rear = r.size();
            }
            return false;
        };
        while (left < right) {
            middle = (left + right) >> 1;
            if (check(middle)) {
                right = middle;
            } else {
                left = middle + 1;
            }
        }
        return left;
    }
};
LeetCode778 水位上升的游泳池中游泳
class Solution {
private:
    int dx[4] = {-1, 1, 0, 0};
    int dy[4] = {0, 0, -1, 1};

public:
    int swimInWater(vector<vector<int>>& grid) {
        int n = grid.size();
        if (n == 1) return grid[0][0];
        auto check = [&](int x) -> bool {
            int que[n * n];
            que[0] = 0;
            vector<bool> vis(n * n, false);
            vis[0] = true;
            int front = 0, rear = 1, tail;
            while (front < rear) {
                tail = rear;
                while (front < tail) {
                    int r = que[front] / n, c = que[front] % n;
                    for (int i = 0; i < 4; ++ i) {
                        int _r = r + dx[i], _c = c + dy[i];
                        int val = _r * n + _c;
                        if (_r >= 0 && _r < n && _c >= 0 && _c < n && !vis[val] && grid[_r][_c] <= x) {
                            if (_r == n - 1 && _c == n - 1) return true;
                            vis[val] = true;
                            que[rear ++] = val;
                        }
                    }
                    ++ front;
                }
            }
            return false;
        };
        int left = grid[0][0], right = n * n, mid;
        while (left < right) {
            mid = left + right >> 1;
            if (check(mid)) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
};
LeetCode1263 推箱子
//力扣官方题解
class Solution {
public:
    int minPushBox(vector<vector<char>>& grid) {
        int m = grid.size(), n = grid[0].size();
        int sx, sy, bx, by; // 玩家、箱子的初始位置
        for (int x = 0; x < m; x++) {
            for (int y = 0; y < n; y++) {
                if (grid[x][y] == 'S') {
                    sx = x;
                    sy = y;
                } else if (grid[x][y] == 'B') {
                    bx = x;
                    by = y;
                }
            }
        }

        auto ok = [&](int x, int y) -> bool { // 不越界且不在墙上
            return x >= 0 && x < m && y >= 0 && y < n && grid[x][y] != '#';
        };
        vector<int> d = {0, -1, 0, 1, 0};

        vector<vector<int>> dp(m * n, vector<int>(m * n, INT_MAX));
        queue<pair<int, int>> q;
        dp[sx * n + sy][bx * n + by] = 0; // 初始状态的推动次数为 0
        q.push({sx * n + sy, bx * n + by});
        while (!q.empty()) {
            queue<pair<int, int>> q1;
            while (!q.empty()) {
                auto [s1, b1] = q.front();
                q.pop();
                int sx1 = s1 / n, sy1 = s1 % n, bx1 = b1 / n, by1 = b1 % n;
                if (grid[bx1][by1] == 'T') { // 箱子已被推到目标处
                    return dp[s1][b1];
                }
                for (int i = 0; i < 4; i++) { // 玩家向四个方向移动到另一个状态
                    int sx2 = sx1 + d[i], sy2 = sy1 + d[i + 1], s2 = sx2*n+sy2;
                    if (!ok(sx2, sy2)) { // 玩家位置不合法
                        continue;
                    }
                    if (bx1 == sx2 && by1 == sy2) { // 推动箱子
                        int bx2 = bx1 + d[i], by2 = by1 + d[i + 1], b2 = bx2*n+by2;
                        if (!ok(bx2, by2) || dp[s2][b2] <= dp[s1][b1] + 1) { // 箱子位置不合法 或 状态已访问
                            continue;
                        }
                        dp[s2][b2] = dp[s1][b1] + 1;
                        q1.push({s2, b2});
                    } else {
                        if (dp[s2][b1] <= dp[s1][b1]) { // 状态已访问
                            continue;
                        }
                        dp[s2][b1] = dp[s1][b1];
                        q.push({s2, b1});
                    }
                }
            }
            q.swap(q1);
        }
        return -1;
    }
};
LeetCode2258 逃离火灾
class Solution {
    /*
    先预处理出全部的方格的最早着火时间
    处理方案是从初始为1的方格进行多源BFS
    每次扩展一格时间就加1
    为了区别开时间2和墙2
    可以将墙置为-1,那么任何时间都大于墙而无法通过
    最后二分枚举可能到达终点的最大停留时间即可
    */
private:
    constexpr static int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    int m, n;

public:
    bool check(vector<vector<int>>& grid, int t) {
        if (grid[0][0] && t >= grid[0][0]) return false;
        vector<pair<int, int>> que;
        que.emplace_back(make_pair(0, 0));
        vector<vector<bool>> vis(m, vector<bool>(n, false));
        vis[0][0] = true;
        int front = 0, rear = 1, tail;
        while (front < rear) {
            tail = rear;
            ++ t;
            while (front < tail) {
                int fir = que[front].first, sec = que[front].second;
                ++ front;
                for (int i = 0; i < 4; ++ i) {
                    int x = fir + dir[i][0], y = sec + dir[i][1];
                    if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && (!grid[x][y] || t <= grid[x][y])) {
                        if (x == m - 1 && y == n - 1) {
                            return true;
                        } else if (t != grid[x][y]) {
                            vis[x][y] = true;
                            que.emplace_back(make_pair(x, y));
                        }
                    }
                }
                rear = que.size();
            }
        }
        return false;
    }

    void bfs(vector<vector<int>>& grid) {
        vector<pair<int, int>> fire;
        for (int i = 0; i < m; ++ i) {
            for (int j = 0; j < n; ++ j) {
                if (grid[i][j] == 1) {
                   fire.emplace_back(make_pair(i, j)); 
                } else if (grid[i][j] == 2) {
                    grid[i][j] = -1;
                }
            }
        }
        int front = 0, rear = fire.size(), tail, t = 1;
        while (front < rear) {
            tail = rear;
            while (front < tail) {
                int fir = fire[front].first, sec = fire[front].second;
                ++ front;
                for (int i = 0; i < 4; ++ i) {
                    int x = fir + dir[i][0], y = sec + dir[i][1];
                    if (x >= 0 && x < m && y >= 0 && y < n && !grid[x][y]) {
                        grid[x][y] = t;
                        fire.emplace_back(make_pair(x, y));
                    }
                }
            }
            ++ t;
            rear = fire.size();
        }
    }

    int maximumMinutes(vector<vector<int>>& grid) {
        m = grid.size(), n = grid[0].size();
        bfs(grid);
        int left = 0, right = m * n, middle, ans = -1;
        while (left <= right) {
            middle = left + right >> 1;
            if (check(grid, middle)) {
                ans = middle;
                left = middle + 1;
            } else {
                right = middle - 1;
            }
        }
        return ans == m * n ? 1e9 : ans;
    }
};
LeetCode2577 在网格图中访问一个格子的最少时间
class Solution {
    /*
    假设初始时,可以从[0, 0]移动到任意相邻点
    那么必定是存在走到一个走到右下角的可行方案的
    因为这样子必定可以来回走消耗时间
    直至可以走向下一个新格子
    假设走到(x, y)
    那么再次走到(x, y)至少需要走2步(离开,回去)
    由此,若从(x0, y0)想走向(x1, y1)
    此时需要判断(x1, y1)的值val1与(x0, y0)的值val2的关系
    若val1 <= val2,下一步可以直接通行
    若val1 > val2,当(val1 - val2) % 2 == 0时,需要(val1 - val2 + 1)步,否则需要(val1 - val2)步
    还不能走的点,可以存到优先队列中,由此可以得到最短可以走的时间
    最后,按照上述思路进行BFS即可
    */
public:
    constexpr static int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    int minimumTime(vector<vector<int>>& grid) {
        if (grid[0][1] > 1 && grid[1][0] > 1) {
            return -1;
        }
        int m = grid.size(), n = grid[0].size(), t = 0;
        vector<vector<bool>> vis(m, vector<bool>(n, false));
        vis[0][0] = true;
        priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<tuple<int, int, int>>> pq;//时间,行,列
        pq.push(tuple<int, int, int>(0, 0, 0));
        while (pq.empty() == false) {
            while (!pq.empty() && t >= get<0>(pq.top())) {
                int r = get<1>(pq.top()), c = get<2>(pq.top());
                if (r == m - 1 && c == n - 1) {
                    return t;
                }
                for (int i = 0; i < 4; ++ i) {
                    int x = r + dir[i][0];
                    int y = c + dir[i][1];
                    if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y]) {
                        vis[x][y] = true;
                        int nt = grid[x][y] <= t ? t + 1 : grid[x][y] + !((grid[x][y] - t) & 1);
                        pq.push(tuple<int, int, int>(nt, x, y));
                    }
                }
                pq.pop();
            }
            t = get<0>(pq.top());
        }
        return -1;
    }
};

总结

知识总结

  1. DFS

DFS 是一种递归或回溯的搜索方法,它从起始节点开始,尽可能深入地探索图的分支,直到无法继续或达到目标节点。在探索过程中,它会标记已经访问过的节点,以避免重复访问。DFS 常用于寻找连通分量、拓扑排序、递归问题等。

  1. BFS

BFS 是一种逐层搜索的方法,它从起始节点开始,逐层遍历相邻节点,直到找到目标节点或遍历完所有节点。BFS 使用队列来存储待访问的节点,按照先进先出的原则进行访问。BFS 常用于寻找最短路径、最小生成树、图的连通性等问题。

  1. 并查集

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

题型总结

  1. DFS

在网格图中,是否存在一条路径(x1, y1)连通(x2, y2)。

  1. BFS

在边长相等的图中寻找(x1, y1)与(x2, y2)之间的最短路。

  1. 图上二分

需要在一个范围内找出图论的最优解。

  1. 图的连通性

可以抽象理解为能否使得图中不同的点在一个集合内。常用DFS、并查集或者Tarjan算法(处理强连通分量)解决。

模板:网格图DFS
constexpr static int DIR[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
int row, column;

Return_Type dfs(vector<vector<Element_Type>>& grid, vector<vector<bool>>& visited, int r, int c) {
	Return_Type ans;// omit if there is no return value
	// begin: core code
	visited[r][c] = true;// use grid to set flags if possible
	for (int i = 0; i < 4; ++ i) {
		int ner = r + DIR[i][0], nec = c + DIR[i][1];
		if (ner >= 0 && ner < row && nec >= 0 && nec < column && !visited[ner][nec]) {
			Return_Type res = dfs(grid, visited, ner, nec);
			// handle the relationship between ans and res
		} 
	}
	// end: core code
	return ans;// omit if there is no return value
}
模板:网格图BFS
Return_Type bfs(vector<vector<Element_Type>>& grid, int startRow, int startColumn) {
	Return_Type ans;// omit if there is no return value
	// begin: core code
	vector<vector<Element_Type>>& visited;// use grid to set flags if possible
	vector<int> r, c;
	r.emplace_back(startRow), c.emplace_back(startColumn);
	visited[startRow][startColumn] = true;
	while (r.size() > 0) {
		vector<int> _r, _c;
		for (int i = 0, sz = r.size(); i < sz; ++ i) {
			for (int j = 0; j < 4; ++ j) {
				int ner = r[i] + DIR[j][0], nec = c[i] + DIR[j][1];
				if (ner >= 0 && ner < row && nec >= 0 && nec < column && !visited[ner][nec]) {
					visited[ner][nec] = true;
					Return_Type res = ......;
					_r.emplace(ner), _c.emplace_back(nec);
					// handle the relationship between ans and res
				} 
			}
		}
		r = move(_r), c = move(_c);
	}
	// end: core code
	return ans;// omit if there is no return value
}
模板:网格图二分查找
bool check(int mid) {
	// execute dfs()/bfs() under the limit of "mid"
	// return false when encountering unsatisfactory situations
	return true;
}

void solve() {
	int le = LEFT, ri = RIGHT, mid;
	while (le < ri) {
		mid = (le + ri) >> 1;
		if (check(mid)) {
			
		} else {
			
		}
	}
}
模板:网格图并查集
int bcj[ELEMENT_NUMBER];

void init() {
	for (int i = 1; i < ELEMENT_NUMBER; ++ i) {
		bcj[i] = i;
	}
}

int find(int x) {
	if (x != bcj[x]) return bcj[x] = find(bcj[x]);
	return x;
}

void merge(int x, int y) {
	bcj[find(x)] = find(y);
}

void solve() {
	// sort the elements of grid
	// use Disjoint-set to handle the elements
}
模板:网格图带权并查集
int bcj[N], sz[N];

bool merge(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y) return false;
    sz[x] += sz[y];
    bcj[y] = x;
    return true;
}

个人理解

  1. DFS

DFS 属于“不撞南墙不回头”的方法,通常会一条路走到黑。因此,DFS更擅长处理只需要寻找某一种合法或者不合法的情况的问题。

  1. BFS

BFS 可以优先访问离起始点较近的节点,因此在寻找最短路径或最小步数等问题上表现较好。在图的连通性上也有不错的表现。

posted on 2024-03-05 14:10  RomanLin  阅读(193)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3