Skip to content

本文转载自力扣讨论区,作者:灵茶山艾府(endlesscheng)。 原文链接:https://leetcode.cn/circle/discuss/YiXPXW/


网格图题单 DFS BFS 网格图题目 图论 灵茶山艾府 灵神 灵神题单

小小贪吃蛇,移动花样多。 平移加旋转,为把迷宫破。 代码复代码,bug 何其多。 六步化一步,AC 定我夺。 —— 1210. 穿过迷宫的最少移动次数

一、网格图 DFS ​

适用于需要计算连通块个数、大小的题目。

部分题目做法不止一种,也可以用 BFS 或并查集解决。

二叉树 DFS 与网格图 DFS 的区别:

二叉树网格图
递归入口根节点网格图的某个格子
递归方向左儿子和右儿子一般为左右上下的相邻格子
递归边界空节点(或者叶节点)出界、遇到障碍或者已访问

模板(计算每个连通块的大小):

py
# 返回网格图 grid 每个连通块的大小
# 时间复杂度 O(mn)
def dfsGrid(grid: List[List[str]]) -> List[int]:
    m, n = len(grid), len(grid[0])
    vis = [[False] * n for _ in range(m)]

    # 返回当前连通块的大小
    def dfs(i: int, j: int) -> int:
        vis[i][j] = True
        size = 1
        for x, y in (i, j - 1), (i, j + 1), (i - 1, j), (i + 1, j):  # 左右上下
            # 这里 grid[x][y] == '.' 根据题意修改
            if 0 <= x < m and 0 <= y < n and grid[x][y] == '.' and not vis[x][y]:
                size += dfs(x, y)
        return size

    comp_size = []
    for i, row in enumerate(grid):
        for j, ch in enumerate(row):
            if ch != '.' or vis[i][j]:  # ch != '.' 根据题意修改
                continue
            size = dfs(i, j)
            comp_size.append(size)
    return comp_size
java
class Solution {
    private static final int[][] DIRS = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; // 左右上下

    // 返回网格图 grid 每个连通块的大小
    // 时间复杂度 O(mn)
    public List<Integer> dfsGrid(char[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        boolean[][] vis = new boolean[m][n];

        List<Integer> compSize = new ArrayList<>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] != '.' || vis[i][j]) { // grid[i][j] != '.' 根据题意修改
                    continue;
                }
                int size = dfs(i, j, grid, vis);
                compSize.add(size);
            }
        }
        return compSize;
    }

    // 返回当前连通块的大小
    private int dfs(int i, int j, char[][] grid, boolean[][] vis) {
        vis[i][j] = true;
        int size = 1;
        for (int[] dir : DIRS) {
            int x = i + dir[0];
            int y = j + dir[1];
            // 这里 grid[x][y] == '.' 根据题意修改
            if (0 <= x && x < grid.length && 0 <= y && y < grid[x].length && grid[x][y] == '.' && !vis[x][y]) {
                size += dfs(x, y, grid, vis);
            }
        }
        return size;
    }
}
cpp
constexpr int DIRS[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; // 左右上下

// 返回网格图 grid 每个连通块的大小
// 时间复杂度 O(mn)
vector<int> dfsGrid(vector<vector<char>>& grid) {
    int m = grid.size(), n = grid[0].size();
    vector vis(m, vector<int8_t>(n));

    // 返回当前连通块的大小
    auto dfs = [&](this auto&& dfs, int i, int j) -> int {
        vis[i][j] = true;
        int size = 1;
        for (auto [dx, dy] : DIRS) {
            int x = i + dx, y = j + dy;
            // 这里 grid[x][y] == '.' 根据题意修改
            if (0 <= x && x < m && 0 <= y && y < n && grid[x][y] == '.' && !vis[x][y]) {
                size += dfs(x, y);
            }
        }
        return size;
    };

    vector<int> comp_size;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (grid[i][j] != '.' || vis[i][j]) { // grid[i][j] != '.' 根据题意修改
                continue;
            }
            int size = dfs(i, j);
            comp_size.push_back(size);
        }
    }
    return comp_size;
}
go
type pair struct{ x, y int }
var dirs = []pair{{0, -1}, {0, 1}, {-1, 0}, {1, 0}} // 左右上下

// 返回网格图 grid 每个连通块的大小
// 时间复杂度 O(mn)
func dfsGrid(grid [][]byte) (compSize []int) {
    m, n := len(grid), len(grid[0])
    vis := make([][]bool, m)
    for i := range vis {
        vis[i] = make([]bool, n)
    }

    // 返回当前连通块的大小
    var dfs func(int, int) int
    dfs = func(i, j int) int {
        vis[i][j] = true
        size := 1
        for _, d := range dirs {
            x, y := i+d.x, j+d.y
            // 这里 grid[x][y] == '.' 根据题意修改
            if 0 <= x && x < m && 0 <= y && y < n && grid[x][y] == '.' && !vis[x][y] {
                size += dfs(x, y)
            }
        }
        return size
    }

    for i, row := range grid {
        for j, ch := range row {
            if ch != '.' || vis[i][j] { // ch != '.' 根据题意修改
                continue
            }
            size := dfs(i, j)
            compSize = append(compSize, size)
        }
    }
    return
}

思维扩展:

二、网格图 BFS ​

适用于需要计算最短距离(最短路)的题目。

DFS 是不撞南墙不回头;BFS 是往水塘中扔石头(起点),荡起一圈圈涟漪(先访问近的,再访问远的)。

模板(单源最短路):

py
# 返回从 (start_x, start_y) 出发,到其余格子的最短距离
# 时间复杂度 O(mn)
def bfsGrid(grid: List[List[str]], start_x: int, start_y: int) -> List[List[int]]:
    m, n = len(grid), len(grid[0])
    dis = [[-1] * n for _ in range(m)]

    dis[start_x][start_y] = 0
    q = deque([(start_x, start_y)])

    while q:
        i, j = q.popleft()
        for x, y in (i, j - 1), (i, j + 1), (i - 1, j), (i + 1, j):  # 左右上下
            # 这里 grid[x][y] == '.' 根据题意修改
            if 0 <= x < m and 0 <= y < n and grid[x][y] == '.' and dis[x][y] < 0:
                dis[x][y] = dis[i][j] + 1
                q.append((x, y))

    return dis
java
class Solution {
    private static final int[][] DIRS = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; // 左右上下

    // 返回从 (startX, startY) 出发,到其余格子的最短距离
    // 时间复杂度 O(mn)
    public int[][] bfsGrid(char[][] grid, int startX, int startY) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] dis = new int[m][n];
        for (int[] row : dis) {
            Arrays.fill(row, -1);
        }

        Queue<int[]> q = new ArrayDeque<>();
        dis[startX][startY] = 0;
        q.offer(new int[]{startX, startY});

        while (!q.isEmpty()) {
            int[] p = q.poll();
            int i = p[0];
            int j = p[1];
            for (int[] dir : DIRS) {
                int x = i + dir[0];
                int y = j + dir[1];
                // 这里 grid[x][y] == '.' 根据题意修改
                if (0 <= x && x < m && 0 <= y && y < n && grid[x][y] == '.' && dis[x][y] < 0) {
                    dis[x][y] = dis[i][j] + 1;
                    q.offer(new int[]{x, y});
                }
            }
        }

        return dis;
    }
}
cpp
constexpr int DIRS[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; // 左右上下

// 返回从 (start_x, start_y) 出发,到其余格子的最短距离
// 时间复杂度 O(mn)
vector<vector<int>> bfsGrid(vector<vector<char>>& grid, int start_x, int start_y) {
    int m = grid.size(), n = grid[0].size();
    vector dis(m, vector<int>(n, -1));
    queue<pair<int, int>> q;

    dis[start_x][start_y] = 0;
    q.emplace(start_x, start_y);

    while (!q.empty()) {
        auto [i, j] = q.front();
        q.pop();
        for (auto [dx, dy] : DIRS) {
            int x = i + dx, y = j + dy;
            // 这里 grid[x][y] == '.' 根据题意修改
            if (0 <= x && x < m && 0 <= y && y < n && grid[x][y] == '.' && dis[x][y] < 0) {
                dis[x][y] = dis[i][j] + 1;
                q.emplace(x, y);
            }
        }
    }

    return dis;
}
go
type pair struct{ x, y int }
var dirs = []pair{{0, -1}, {0, 1}, {-1, 0}, {1, 0}} // 左右上下

// 返回从 (startX, startY) 出发,到其余格子的最短距离
// 时间复杂度 O(mn)
func bfsGrid(grid [][]byte, startX, startY int) [][]int {
    m, n := len(grid), len(grid[0])
    dis := make([][]int, m)
    for i := range dis {
        dis[i] = make([]int, n)
        for j := range dis[i] {
            dis[i][j] = -1
        }
    }

    dis[startX][startY] = 0
    q := []pair{{startX, startY}}

    for len(q) > 0 {
        p := q[0]
        q = q[1:]
        for _, d := range dirs {
            x, y := p.x+d.x, p.y+d.y
            // 这里 grid[x][y] == '.' 根据题意修改
            if 0 <= x && x < m && 0 <= y && y < n && grid[x][y] == '.' && dis[x][y] < 0 {
                dis[x][y] = dis[p.x][p.y] + 1
                q = append(q, pair{x, y})
            }
        }
    }

    return dis
}

三、网格图 0-1 BFS ​

边权只有 0 和 1 的题目,也可以用 BFS 做。

四、网格图 Dijkstra ​

见 图论题单 中的「§3.1 单源最短路:Dijkstra 算法」,我标记了网格图的题目。

五、综合应用 ​

思考题 ​

  1. 对于 m 行 n 列的网格图,BFS 的队列最多消耗多少空间?DFS 的递归栈最多消耗多少空间?
  2. 构造一个网格图,让 DFS 的递归深度尽量大。如果起点是 (0,0) 要如何构造?如果起点是 (i,j) 要如何构造?

欢迎在评论区发表你的思路。

算法题单 ​

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

如果你发现有题目可以补充进来,欢迎评论反馈。