LeetCode990.等式方程的可满足性(并查集实现)

等式方程的可满足性(并查集实现)

该文章记录了作者的思考过程,可能比较繁琐,如果想直接看答案思路的,直接看分析1.0,2.0以及最终代码。

先看题目:

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

示例 1:
输入:["a==b","b!=a"]
输出:false
解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量
同时满足这两个方程。
示例 2:
输入:["b==a","a==b"]
输出:true
解释:我们可以指定 a = 1 且 b = 1 以满足满足这两个方程。

示例 3:
输入:["a==b","b==c","a==c"]
输出:true

示例 4:
输入:["a==b","b!=c","c==a"]
输出:false

示例 5:
输入:["c==c","b==d","x!=z"]
输出:true

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/satisfiability-of-equality-equations
最初想法:

刚开始开这道题目的时候,第一时间想到的是用hash表存储,每对相等的两个元素都有一个
hash表,当遍历到相等的元素时,直接判断是否在hash表中,在则返回false。但是后面想了一下,很难实现,因为n个表达式,最多可能需要存2n-1个hash表,每个hash表的储值最多可达n-1,这个太大了。

分析1.0:

看了一下官方答案,才知道用并查集的思想去实现,主要思路是:
1.先遍历相等的表达式,如果他们没有相同的父节点,就将其中一个作为另外一个父节点;如果有相同父节点则跳过。
比如:a == b, b ==c, c ==d; 图形表示为:d->c->b->a;
用代码实是:parent[d]=c;parent[c]=b;parent[b]=a;
即将多个值相同的变量,连接起来,在该序列的任意变量,都可以通过循环获取其最终父节点(比如a)
2.再遍历不相等的表达式,只要该表达式的两个变量拥有共同的最终父节点,则证明他们相等返回false,如果没有冲突最后返回ture。

下面c++实现代码(还可优化,如果只想看最终答案,往下滑):

	int findFa(int parent[], int x) {
    
    
		// 通过递归获取最终父节点
        return (parent[x] == x ? x : findFa(parent, parent[x]));
    }
    bool equationsPossible(vector<string>& equations) {
    
    
        int parent[26];
        int child[26];
        for (int i = 0; i < 26; i++) {
    
    
            parent[i] = i;		// 父节点
            child[i] = i;		// 子节点
        }
        for (auto a : equations) {
    
    
            if (a[1] == '=') {
    
    
                int x1 = a[0] - 'a';
                int y1 = a[3] - 'a';
                int x = findFa(parent, x1);
                int y = findFa(parent, y1);
                // 父节点不同
                if (x != y) {
    
    
                	// 该节点有父节点
                    if (x != x1) {
    
    
	                    //获取 x1的最新节点对接y1的最终父节点
	                    //比如: 已有a->b->c 和 d->e->f两个序列,遍历到: b->e时
	                    //对接的应该是 c->d 即 a->b->c->d->e->f
                        parent[y] = findFa(child, x1);
                        child[findFa(child, x1)] = y;
                    }
                    else if (y != y1) {
    
    
                        parent[x] = findFa(child, y1);
                        child[findFa(child, y1)] = x;
                    }
                    else {
    
    
                    	// 当 x1, x2 都没有父节点时,直接赋值
                        parent[y1] = x1;
                        child[x1] = y1;
                    }
                }
            }
        }
        for (auto a : equations) {
    
    
            if (a[1] == '!') {
    
    
            	//如果有相同父节点,则矛盾,返回false
                if (findFa(parent, a[0] - 'a') == findFa(parent, a[3] - 'a')) {
    
    
                    return false;
                }
            }
        }
        return true;
    }

分析2.0:

其实我们只需要一个节点的最终父节点,所以路径经过的节点并不关心,所以我们可以采用路径压缩的方式,比如上段代码提到的:
已有a->b->c 和 d->e->f两个序列,遍历到: b->e时,我们可以直接将 两个父节点对接,即:
a->d->e->f;这样e的最终父节点是a, b的最终父节点也还是a,同样满足条件。如此可省略一个child数组。

最终实现代码是:

	int findFa(int parent[], int x) {
    
    
        while (x != parent[x]) {
    
    	//用循环代替递归,减少使用堆栈空间。
            parent[x] = parent[parent[x]];	// 进行路由压缩(只需要最终父节点,不需要在意中间节点)
            x = parent[x];
        }
        return x;
    }

    bool equationsPossible(vector<string>& equations) {
    
    
        int parent[26];
        for (int i = 0; i < 26; i++) {
    
    
            parent[i] = i;
        }
        for (auto eq : equations) {
    
    
            if (eq[1] == '=') {
    
    
                int fa1 = findFa(parent, eq[0] - 'a');
                int fa2 = findFa(parent, eq[3] - 'a');
                if (fa1 != fa2) {
    
    
                    parent[fa2] = fa1;	//父节点对接父节点
                }
            }
        }

        for (auto eq : equations) {
    
    
            if (eq[1] == '!') {
    
    
                if (findFa(parent, eq[0] - 'a') == findFa(parent, eq[3] - 'a')) {
    
    
                    return false;
                }
            }
        }
        return true;
    }

最后:已经写完了,如果有描述不够清晰或者错误之处,可留言交流。

猜你喜欢

转载自blog.csdn.net/h799710/article/details/106668357