LeetCode 热题 100 精讲 | 并查集篇:最长连续序列 · 岛屿数量 · 省份数量 · 冗余连接 · 等式方程的可满足性

一、128. 最长连续序列

🔗 题目链接

LeetCode 128. 最长连续序列

📝 题目描述

给定一个未排序的整数数组 nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。要求设计并实现时间复杂度为 O(n) 的算法。

示例

复制代码
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长连续序列是 [1,2,3,4],长度为 4。

🧠 思路分析

这道题最经典的解法是用哈希表,但用并查集同样能解,且能加深对并查集理解。并查集的核心是合并连续的数字,每个数字自成一个集合,遍历数组时,如果 x+1 在数组中,就合并 xx+1 所在的集合,并统计合并后的集合大小。由于数组元素可能很大,不能用直接开数组的方式初始化,需要用哈希表来存储每个数字对应的父节点。合并时路径压缩和按秩合并两个优化都要加上,这样每次 find 和 union 操作近似 O(1)。最后遍历所有数字,找到最大的集合大小即可。这个解法的时间复杂度是 O(n·α(n)),近似 O(n)。

💻 代码实现(C++)

cpp 复制代码
class UnionFind {
    unordered_map<int, int> parent;
    unordered_map<int, int> size;
public:
    int find(int x) {
        if (!parent.count(x)) return x;
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }
    void unite(int x, int y) {
        x = find(x); y = find(y);
        if (x == y) return;
        if (size[x] < size[y]) swap(x, y);
        parent[y] = x;
        size[x] += size[y];
    }
    void add(int x) {
        if (!parent.count(x)) {
            parent[x] = x;
            size[x] = 1;
        }
    }
    int getSize(int x) {
        return size[find(x)];
    }
};

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        if (nums.empty()) return 0;
        UnionFind uf;
        unordered_set<int> numSet(nums.begin(), nums.end());
        for (int x : numSet) {
            uf.add(x);
            if (numSet.count(x + 1)) uf.unite(x, x + 1);
        }
        int maxLen = 1;
        for (int x : numSet) {
            maxLen = max(maxLen, uf.getSize(x));
        }
        return maxLen;
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(n·α(n)),近似 O(n)。

  • 空间复杂度:O(n),存储父节点和集合大小。


二、200. 岛屿数量

🔗 题目链接

LeetCode 200. 岛屿数量

📝 题目描述

给你一个由 '1'(陆地)和 '0'(水)组成的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。你可以假设网格的四个边均被水包围。

示例

复制代码
输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

🧠 思路分析

并查集解法将每个值为 '1' 的格子看成一个节点,将上下左右相邻的格子合并到同一个集合中。初始化时,每个 '1' 格子自成一个集合。遍历整个网格,对于每个 '1',检查它上方和左方是否有 '1',有则合并,因为从上到下从左到右遍历时,每个格子只需要检查上方和左方就能覆盖所有相邻关系。最终,集合的数量就是岛屿的数量。这种解法与 DFS/BFS 的不同之处在于,并查集能更好地处理动态添加陆地的情况,比如在线算法场景。

💻 代码实现(C++)

cpp 复制代码
class UnionFind {
    vector<int> parent;
    vector<int> rank;
    int count;
public:
    UnionFind(int n) : parent(n), rank(n, 1), count(n) {
        for (int i = 0; i < n; i++) parent[i] = i;
    }
    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }
    void unite(int x, int y) {
        int rx = find(x), ry = find(y);
        if (rx == ry) return;
        if (rank[rx] < rank[ry]) parent[rx] = ry;
        else if (rank[rx] > rank[ry]) parent[ry] = rx;
        else { parent[ry] = rx; rank[rx]++; }
        count--;
    }
    int getCount() { return count; }
};

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        int m = grid.size(), n = grid[0].size();
        UnionFind uf(m * n);
        int waterCount = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '0') {
                    waterCount++;
                    continue;
                }
                int idx = i * n + j;
                if (i > 0 && grid[i-1][j] == '1') uf.unite(idx, (i-1)*n + j);
                if (j > 0 && grid[i][j-1] == '1') uf.unite(idx, i*n + (j-1));
            }
        }
        return uf.getCount() - waterCount;
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(m×n·α(m×n)),每个格子最多被访问两次。

  • 空间复杂度:O(m×n),存储父节点数组。


三、547. 省份数量

🔗 题目链接

LeetCode 547. 省份数量

📝 题目描述

n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。省份是一组直接或间接相连的城市,组内不含其他没有相连的城市。给你一个 n × n 的矩阵 isConnected,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,isConnected[i][j] = 0 表示二者不直接相连。返回矩阵中省份的数量。

示例

复制代码
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2

🧠 思路分析

这道题是并查集最直接的入门题,本质上就是求图中的连通分量数。初始化时,n 个城市各自独立成一个集合。遍历对称矩阵的上三角部分,如果 isConnected[i][j] == 1,就将 ij 合并到同一个集合中。遍历结束后,统计有多少个集合的根节点是自己,这个数量就是省份的数量。并查集的查找和合并都要用路径压缩,否则在极端情况下会退化到 O(n²)。这道题也可以 DFS 或 BFS 做,但并查集解法更直观地体现了"集合合并"的语义。

💻 代码实现(C++)

cpp 复制代码
class UnionFind {
    vector<int> parent;
    vector<int> rank;
public:
    UnionFind(int n) : parent(n), rank(n, 1) {
        for (int i = 0; i < n; i++) parent[i] = i;
    }
    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }
    void unite(int x, int y) {
        int rx = find(x), ry = find(y);
        if (rx == ry) return;
        if (rank[rx] < rank[ry]) parent[rx] = ry;
        else if (rank[rx] > rank[ry]) parent[ry] = rx;
        else { parent[ry] = rx; rank[rx]++; }
    }
    int countProvinces() {
        int cnt = 0;
        for (int i = 0; i < parent.size(); i++) {
            if (parent[i] == i) cnt++;
        }
        return cnt;
    }
};

class Solution {
public:
    int findCircleNum(vector<vector<int>>& isConnected) {
        int n = isConnected.size();
        UnionFind uf(n);
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (isConnected[i][j]) uf.unite(i, j);
            }
        }
        return uf.countProvinces();
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(n²·α(n)),遍历矩阵的上三角部分。

  • 空间复杂度:O(n),存储父节点和秩数组。


四、684. 冗余连接

🔗 题目链接

LeetCode 684. 冗余连接

📝 题目描述

在本问题中,树指的是一个连通且无环的无向图。输入一个图,该图由一个有着 N 个节点(节点值不重复 1, 2, ..., N)的树及一条附加的边构成。附加的边的两个顶点包含在 1 到 N 中间,这条附加的边不属于树中已存在的边。结果图是一个以边组成的二维数组。返回一条可以删去的边,使得结果图是一个有着 N 个节点的树。如果有多个答案,则返回二维数组中最后出现的边。

示例

复制代码
输入:[[1,2], [1,3], [2,3]]
输出:[2,3]

🧠 思路分析

这道题完美展现了并查集在图环检测中的威力。一棵树有 N 个节点和 N-1 条边,题目给出的图有 N 条边,因此有且只有一个环。遍历所有边,对于每条边 [u, v],检查 uv 是否已经在同一个集合中。如果不在,说明这条边不会形成环,合并这两个节点;如果已经在同一个集合中,说明这条边会形成环,它就是我们要找的冗余连接。这道题的精妙之处在于,不需要额外判断哪条边是最后一条,只需按顺序处理,第一条导致环的边就是答案(因为题目要求返回最后出现的边,但实际处理中第一条检测到环的边就是那个闭环的边)。

💻 代码实现(C++)

cpp 复制代码
class UnionFind {
    vector<int> parent;
public:
    UnionFind(int n) : parent(n + 1) {
        for (int i = 1; i <= n; i++) parent[i] = i;
    }
    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }
    bool unite(int x, int y) {
        int rx = find(x), ry = find(y);
        if (rx == ry) return false;
        parent[rx] = ry;
        return true;
    }
};

class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        int n = edges.size();
        UnionFind uf(n);
        for (auto& edge : edges) {
            if (!uf.unite(edge[0], edge[1])) return edge;
        }
        return {};
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(n·α(n)),遍历每条边。

  • 空间复杂度:O(n),存储父节点数组。


五、990. 等式方程的可满足性

🔗 题目链接

LeetCode 990. 等式方程的可满足性

📝 题目描述

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:"a==b""a!=b"。其中 ab 是小写字母(不一定不同),表示单字母变量名。只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false

示例

复制代码
输入:["a==b","b!=a"]
输出:false

🧠 思路分析

这道题是用并查集处理约束关系的最典型例子。等式具有传递性:a==bb==c 可以推出 a==c,因此可以用并查集将所有相等的变量合并到同一个集合中。具体做法是:先遍历所有等式方程,将等式两边的字母合并;然后遍历所有不等式方程,检查不等式两边的字母是否在同一个集合中------如果在,说明它们既被要求相等又被要求不相等,产生了矛盾,直接返回 false。如果所有不等式都通过检查,返回 true。由于只有 26 个小写字母,可以用固定大小的数组实现并查集,非常高效。

💻 代码实现(C++)

cpp 复制代码
class UnionFind {
    vector<int> parent;
public:
    UnionFind() : parent(26) {
        for (int i = 0; i < 26; i++) parent[i] = i;
    }
    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }
    void unite(int x, int y) {
        parent[find(x)] = find(y);
    }
};

class Solution {
public:
    bool equationsPossible(vector<string>& equations) {
        UnionFind uf;
        for (string& eq : equations) {
            if (eq[1] == '=') {
                uf.unite(eq[0] - 'a', eq[3] - 'a');
            }
        }
        for (string& eq : equations) {
            if (eq[1] == '!') {
                if (uf.find(eq[0] - 'a') == uf.find(eq[3] - 'a')) return false;
            }
        }
        return true;
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(n·α(26)),n 为方程的数量。

  • 空间复杂度:O(26),并查集大小固定。


六、并查集模板代码

并查集(Disjoint Set Union)是处理动态连通性问题最优雅的数据结构。核心操作有两个:find 查找元素所属集合的根节点,union 合并两个集合。为了提高效率,通常配合两种优化手段:路径压缩 (在查找过程中将沿途节点的父节点直接指向根节点)和按秩合并(将节点数较少的集合合并到节点数较多的集合中)。以下是 C++ 的标准模板:

cpp 复制代码
class UnionFind {
private:
    vector<int> parent;
    vector<int> rank;  // 按秩合并:记录树的高度

public:
    UnionFind(int n) {
        parent.resize(n);
        rank.resize(n, 1);
        for (int i = 0; i < n; i++) parent[i] = i;
    }

    // 查找根节点 + 路径压缩
    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    // 合并两个集合
    bool unite(int x, int y) {
        int rx = find(x), ry = find(y);
        if (rx == ry) return false;  // 已属于同一集合
        // 按秩合并:将高度较低的树接到高度较高的树上
        if (rank[rx] < rank[ry]) {
            parent[rx] = ry;
        } else if (rank[rx] > rank[ry]) {
            parent[ry] = rx;
        } else {
            parent[ry] = rx;
            rank[rx]++;
        }
        return true;
    }

    // 判断两个元素是否属于同一集合
    bool connected(int x, int y) {
        return find(x) == find(y);
    }

    // 统计集合数量
    int count() {
        int cnt = 0;
        for (int i = 0; i < parent.size(); i++) {
            if (parent[i] == i) cnt++;
        }
        return cnt;
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。


结语

并查集篇的五道题覆盖了并查集的几种典型应用场景:连续序列合并 (最长连续序列)、二维网格连通 (岛屿数量)、连通分量计数 (省份数量)、环检测 (冗余连接)、约束满足问题(等式方程)。并查集的核心在于处理"动态连通性"问题------两个元素是否属于同一个集合,以及如何高效地合并集合。加上路径压缩和按秩合并两种优化后,单次操作近乎 O(1),可以轻松应对大规模数据。

建议刷题顺序:先做 547 和 200 熟悉并查集的基本模板,再做 684 理解环检测的应用,然后做 128 挑战哈希映射版并查集,最后用 990 收尾体会约束问题的建模思路。

如果本文对你有帮助,欢迎点赞、收藏、转发,你的支持是我持续创作的动力 ❤️

免责声明:本文部分解题思路参考了力扣官方题解及社区优秀文章,相关链接均来自公开网络。若存在侵权问题,请联系删除。

相关推荐
2501_921960853 小时前
双相自指图与弦论边界非对易性的结构同源
数据结构
王老师青少年编程3 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【线性扫描贪心】:均分纸牌
c++·算法·编程·贪心·csp·信奥赛·均分纸牌
EQUINOX13 小时前
2026年码蹄杯 本科院校赛道&青少年挑战赛道提高组初赛(省赛)第一场,个人题解
算法
萝卜小白3 小时前
算法实习Day04-MinerU2.5-pro
人工智能·算法·机器学习
Liangwei Lin3 小时前
洛谷 P3133 [USACO16JAN] Radio Contact G
数据结构·算法
weixin_513449963 小时前
PCA、SVD 、 ICP 、kd-tree算法的简单整理总结
c++·人工智能·学习·算法·机器人
code_pgf3 小时前
Qwen2.5-VL 算法解析
人工智能·深度学习·算法·transformer
烟锁池塘柳03 小时前
一文讲透 C++ / Java 中方法重载(Overload)与方法重写(Override)在调用时机等方面的区别
java·c++·面向对象
Code-keys4 小时前
Android Codec2 Filter 算法模块开发指南
android·算法·音视频·视频编解码