图论专题(二十二):并查集的“逻辑审判”——判断「等式方程的可满足性」

哈喽各位,我是前端小L。

欢迎来到我们的图论专题第二十二篇!并查集(Union-Find)最擅长维护的是什么?是**"集合"。而在逻辑学中,"相等"关系(Equality)天然就具有 传递性**(若 a==b 且 b==c,则 a==c),这正好对应了并查集中的连通性

今天,我们要面对一组混杂着 ==!= 的方程组。

  • == 代表"连接"(Union)。

  • != 代表"冲突检查"(Check)。

我们的策略非常清晰:先把所有宣称是"朋友"的人聚在一起,然后再检查那些宣称是"敌人"的人,有没有混在同一个圈子里。

力扣 990. 等式方程的可满足性

https://leetcode.cn/problems/satisfiability-of-equality-equations/

题目分析:

  • 输入 :一个字符串数组 equations。每个字符串形如 "a==b""a!=b"。变量名只有小写字母 'a''z'

  • 目标 :判断这组方程是否逻辑自洽。如果存在矛盾(例如 a==b, b==ca!=c),返回 false;否则返回 true

核心洞察:处理顺序是关键 如果我们一边处理 == 一边处理 !=,可能会出问题。 比如:a!=b, b==c, a==c

  1. a!=b:目前 a 和 b 不连通,没毛病。

  2. b==c:连通 b 和 c。

  3. a==c:连通 a 和 c。 这就漏掉了矛盾!因为 ac 连通后,通过 c 这个中介,ab 其实也连通了,这就违背了第一条 a!=b

正确的策略

  1. 先礼 :无视所有 !=,先把所有 == 的关系处理完,构建好所有的"等价集合"(连通分量)。

  2. 后兵 :遍历所有 != 的关系,检查每一对变量。如果它们在并查集中竟然拥有同一个老大 (即它们是等价的),那就自相矛盾了!

算法流程:两遍扫描

  1. 初始化并查集

    • 由于变量只是 'a'-'z',我们只需要一个大小为 26 的 parent 数组。parent[i] = i
  2. 第一遍扫描 (==)

    • 遍历所有方程。如果中间符号是 ==eq[1] == '='):

      • 提取变量 u = eq[0] - 'a', v = eq[3] - 'a'

      • 执行 union(u, v)

  3. 第二遍扫描 (!=)

    • 再次遍历所有方程。如果中间符号是 !=eq[1] == '!'):

      • 提取变量 u, v

      • 审判时刻 :检查 find(u) 是否等于 find(v)

      • 如果相等,说明 uv 在之前的等式逻辑中已经被判定为"相等"了,现在你又说它俩不等,这就是逻辑矛盾 !直接返回 false

  4. 通过 :如果所有 != 检查都平安无事,返回 true

代码实现 (轻量级并查集)

因为只需要处理26个字母,我们可以直接把并查集的逻辑写在主函数里,或者写一个极简的内部类。

C++

复制代码
#include <vector>
#include <string>
#include <numeric>

using namespace std;

class Solution {
private:
    // 简单的并查集实现
    vector<int> parent;

    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]); // 路径压缩
        }
        return parent[x];
    }

    void unite(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            parent[rootX] = rootY;
        }
    }

public:
    bool equationsPossible(vector<string>& equations) {
        // 1. 初始化并查集,容量 26
        parent.resize(26);
        iota(parent.begin(), parent.end(), 0); // 0, 1, ..., 25

        // 2. 第一遍扫描:处理所有 "=="
        for (const string& eq : equations) {
            if (eq[1] == '=') {
                int u = eq[0] - 'a';
                int v = eq[3] - 'a';
                unite(u, v);
            }
        }

        // 3. 第二遍扫描:检查所有 "!="
        for (const string& eq : equations) {
            if (eq[1] == '!') {
                int u = eq[0] - 'a';
                int v = eq[3] - 'a';
                // 如果 u 和 v 属于同一个集合(相等),但等式要求不等 -> 矛盾
                if (find(u) == find(v)) {
                    return false;
                }
            }
        }

        // 4. 没有矛盾
        return true;
    }
};

深度复杂度分析

  • N:方程的数量。

  • 时间复杂度 O(N)

    • 我们遍历了方程数组两次。

    • 每次并查集操作(find/unite)在路径压缩下接近 O(1)(准确说是阿克曼反函数,对于26个节点简直快到飞起)。

    • 所以总时间是线性的。

  • 空间复杂度 O(1)

    • parent 数组的大小固定为 26,是常数级空间。

总结:并查集------处理等价关系的专家

今天这道题,展示了并查集在逻辑推理中的应用。它告诉我们:

凡是涉及"分组"、"归类"、"等价传递"的问题,都可以抽象为并查集的模型。

  • Union = 建立等价关系。

  • Find = 验证等价关系。

通过巧妙的两遍扫描(先建关系,后查冲突),我们轻松破解了这个逻辑谜题。

在下一篇中,我们将挑战一个更加复杂、更贴近实际业务的并查集应用------"账户合并"。当一个用户拥有多个邮箱,而不同的邮箱列表又存在交集时,我们该如何理清这些混乱的账户关系?

下期见!

相关推荐
智者知已应修善业38 分钟前
【51单片机8位数码管同时倒计时从9999】2024-1-25
c++·经验分享·笔记·算法·51单片机
洛水水41 分钟前
【力扣100题】86.柱状图中最大的矩形
算法·leetcode·职场和发展
渡之1 小时前
GRiM-Net 深度解析 | 无人机 GNSS 拒止场景下两阶段跨视角视觉定位框架
深度学习·算法·动态规划·无人机
测试仪器廖生135902563851 小时前
罗德与施瓦茨 FSP13频谱分析仪FSP30
网络·人工智能·算法
happymaker06261 小时前
LeetCodeHot100——560.和为K的子数组
算法
dtq04241 小时前
C语言刷题数组5,6(求平均值,求最大值)
c语言·数据结构·算法
郭梧悠2 小时前
Hash算法入门Hash冲突解决方案
算法·哈希算法
洛水水2 小时前
【力扣100题】81.寻找两个正序数组的中位数
数据结构·算法·leetcode
happymaker06263 小时前
LeetCodeHot100——155.最小栈
算法
洛水水3 小时前
【力扣100题】85.每日温度
算法·leetcode·职场和发展