LeetCode 补拙笔记 日期:2026.05.29 题目:1559. 二维网格图中探测环

LeetCode 补拙笔记

0. 前言

  • 日期:2026.05.29
  • 题目:1559. 二维网格图中探测环
  • 难度:中等
  • 标签:并查集、图论、DFS

1. 题目理解

问题描述

给定一个二维字符网格 grid,判断网格中是否存在由相同字符 构成的环。

环的定义是:一条长度 ≥ 4 的路径,起点和终点为同一个格子,路径中不能直接回到上一步所在的格子。

示例

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

输出:true

解释:外层的 a 和内层的 b 都各自形成了环。

2. 解题思路

核心观察

  • 环的存在等价于在无向图中,两个连通的节点被再次合并时,会形成环。
  • 可以用并查集(Union-Find) 解决:遍历相邻格子,若字符相同则尝试合并,合并前发现两节点已连通,说明存在环。

算法步骤

  1. 初始化并查集,每个格子为独立集合。
  2. 遍历网格,对每个格子只检查右侧和下侧的邻居(避免重复检查)。
  3. 若当前格子与邻居字符相同,执行合并操作。
  4. 合并时若发现两节点已连通,说明形成了环,直接返回 true
  5. 遍历结束未发现环,返回 false

3. 代码实现

java 复制代码
package lc1559;

class Solution {
    public boolean containsCycle(char[][] grid) {
        int n = grid.length;
        if (n == 0) return false;
        int m = grid[0].length;

        int[] parent = new int[n * m];
        for (int i = 0; i < n * m; i++) {
            parent[i] = i;
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                char c = grid[i][j];
                // 向右看
                if (j + 1 < m && grid[i][j + 1] == c) {
                    int u = i * m + j;
                    int v = i * m + (j + 1);
                    if (union(parent, u, v)) {
                        return true;
                    }
                }

                if (i + 1 < n && grid[i + 1][j] == c) {
                    int u = i * m + j;
                    int v = (i + 1) * m + j;
                    if (union(parent, u, v)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    private int find(int[] parent, int x) {
        int root = x;
        while (parent[root] != root) {
            root = parent[root];
        }

        while (parent[x] != root) {
            int next = parent[x];
            parent[x] = root;
            x = next;
        }
        return root;
    }

    private boolean union(int[] parent, int x, int y) {
        int rootX = find(parent, x);
        int rootY = find(parent, y);
        if (rootX == rootY) {
            return true;
        }
        parent[rootY] = rootX;
        return false;
    }
}

4. 代码优化说明

(代码未做任何修改,仅添加注释讲解)

java 复制代码
class Solution {
public boolean containsCycle(char[][] grid) {
    int len1=grid.length;
    int len2=grid[0].length;
    // 并查集数组,索引表示格子编号,值为父节点
    int[] a=new int[len1*len2];
    a[0]=0;
    // 初始化第一行的父节点
    for (int j=1; j<len2; j++){
        // 若与左侧字符相同,继承左侧的父节点;否则父节点为自身
        if (grid[0][j]==grid[0][j-1]){
            a[j]=a[j-1];
        }else{
            a[j]=j;
        }
    }
    int k=len2;
    // 遍历后续每一行
    for (int i=1; i<len1; i++){
        // 初始化每行第一个格子的父节点
        if (grid[i][0]==grid[i-1][0]){
            a[k]=a[i*len2-len2];
        }else{
            a[k]=k;
        }
        k++;
        // 遍历每行后续的格子
        for (int j=1; j<len2; j++, k++){
            // 先处理左侧邻居
            if (grid[i][j]==grid[i][j-1]){
                a[k]=a[k-1];
            }else{
                a[k]=k;
            }
            // 再处理上方邻居,判断是否形成环
            if (grid[i][j]==grid[i-1][j]){
                // 若当前格子与上方格子已连通,则存在环
                if (fn(a, k)==fn(a, k-len2)){
                    return true;
                }else{
                    // 合并两个集合
                    a[a[k-len2]]=a[k];
                }
            }
        }
    }
    return false;
}
// 路径压缩的查找函数
public int fn(int[] a, int k){
    if (a[k]!=k){
        a[k]=fn(a, a[k]);
    }
    return a[k];
}
}

5. 复杂度分析

  • 时间复杂度 :O(n×m⋅α(n×m))O(n \times m \cdot \alpha(n \times m))O(n×m⋅α(n×m))
    其中 nnn 和 mmm 为网格的行列数,α\alphaα 为阿克曼函数的反函数,近似为常数。并查集的查找和合并操作几乎是常数时间。
  • 空间复杂度 :O(n×m)O(n \times m)O(n×m)
    主要为并查集数组的开销。

6. 总结

  • 核心思路:并查集检测环,通过合并相同字符的相邻节点,利用连通性判断环的存在。
  • 优化后代码在遍历顺序和并查集初始化上做了简化,逻辑更紧凑。
  • 关键技巧:只检查右侧和下侧邻居,避免重复合并;路径压缩优化查找效率。
相关推荐
罗超驿1 小时前
10.滑动窗口解决:无重复字符的最长子串 | LeetCode 3 Java 题解
java·算法·leetcode·面试
罗超驿1 小时前
8.【LeetCode 18】四数之和 —— Java 排序 + 双指针解法详解
算法·leetcode·职场和发展
菜菜的顾清寒1 小时前
HOT100力扣(40) 动态规划-爬楼梯
算法·leetcode·动态规划
不羁的木木2 小时前
ArkWeb实战学习笔记02-环境搭建与基础配置
笔记·学习·harmonyos
m沐沐2 小时前
【机器学习】聚类算法-K-means聚类
人工智能·python·算法·机器学习·pycharm·kmeans·聚类
stars-he2 小时前
SPICE编程与仿真学习笔记:从网表到瞬态分析
笔记·学习·硬件工程
z落落2 小时前
C# Dictionary 字典集合+数组、List、Dictionary 三者终极对比
算法
醇氧2 小时前
排队论(牛吃草问题)解题全解析
算法
.千余2 小时前
【C++】C++核心语法:函数重载与缺省参数原理与避坑
c语言·开发语言·c++·经验分享·笔记·git·学习