前端算法第一九七弹-二维网格图中探测环

177 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情

给你一个二维字符网格数组 grid ,大小为 m x n ,你需要检查 grid 中是否存在 相同值 形成的环。

一个环是一条开始和结束于同一个格子的长度 大于等于 4 的路径。对于一个给定的格子,你可以移动到它上、下、左、右四个方向相邻的格子之一,可以移动的前提是这两个格子有 相同的值

同时,你也不能回到上一次移动时所在的格子。比方说,环 (1, 1) -> (1, 2) -> (1, 1) 是不合法的,因为从 (1, 2) 移动到 (1, 1) 回到了上一次移动时的格子。

如果 grid 中有相同值形成的环,请你返回 true ,否则返回 false 。

示例 1:

图片.png

输入:grid = [["a","a","a","a"],["a","b","b","a"],["a","b","b","a"],["a","a","a","a"]]
输出:true

示例 2:

图片.png

输入:grid = [["c","c","c","a"],["c","d","c","c"],["c","c","e","c"],["f","c","c","c"]]
输出:true

示例 3:

图片.png

输入:grid = [["a","b","b"],["b","z","b"],["b","b","a"]]
输出:false

并查集

使用并查集判断无向图中是否有环的方法非常简洁且直观:

  • 对于图中的任意一条边 (x,y)(x,y),我们将 xxyy 对应的集合合并。如果 xxyy 已经属于同一集合,那么说明 xxyy 已经连通,在边 (x,y)(x,y) 的帮助下,图中会形成一个环。

这样一来,我们只要遍历图中的每一条边并进行上述的操作即可。具体的方法是,我们遍历数组 grid\textit{grid} 中的每一个位置,如果该位置与其上方或左侧的值相同,那么就有了一条边,并将这两个位置进行合并。这样的方法可以保证每一条边的两个节点只会被合并一次。

由于并查集是一维的数据结构,而数组 grid\textit{grid} 是二维的。因此对于数组中的每个位置 (i,j)(i,j),我们可以用 i×n+ji \times n + j 将其映射至一维空间中:

  • (i,j)(i,j) 上方的位置对应着 (i1)×n+j(i - 1) \times n + j
  • (i,j)(i,j) 左侧的位置对应着 i×n+j1i \times n + j - 1
/**
 * @param {character[][]} grid
 * @return {boolean}
 */
var containsCycle = function(grid) {
    let searched = new Array(grid.length).fill(0).map(i => new Array(grid[0].length).fill(false)); // 记录搜索过的元素,防止重复
    function search(i, j, pre = null, path = new Set()) {
        //  进行搜索,探寻到路径中重复的元素,说明成环,返回true.
        if (path.has(i+'-'+j)) {
            return true;
        }
        let pos = [[-1,0], [1,0], [0,1], [0,-1]];
        let can = false;
        path.add(i+'-'+j);
        for (let [x,y] of pos) {
            if (i+x >= 0 && i+x < grid.length && j+y >= 0 && j+y < grid[0].length && grid[i+x][j+y] === grid[i][j]) {
                if (`${i+x}-${j+y}` === pre) continue; // 不能重复走上一次走过的格子
                can |= search(i+x, j+y, i+'-'+j, path);
            }
        }
        searched[i][j] = true;
        path.delete(i+'-'+j);
        return can;
    }
    for (let i=0; i<grid.length; i++) {
        for (let j=0; j<grid[0].length; j++) {
            if (!searched[i][j] && search(i,j)) return true; // 遍历,找到了立即返回结果
        }
    }
    return false;
};