echo

任生命穿梭 时间的角落

0%

等式方程的可满足性

990. 等式方程的可满足性

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:"a==b""a!=b"。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。

只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false

示例 1:

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

示例 2:

1
2
3
输出:["b==a","a==b"]
输入:true
解释:我们可以指定 a = 1 且 b = 1 以满足满足这两个方程。

示例 3:

1
2
输入:["a==b","b==c","a==c"]
输出:true

示例 4:

1
2
输入:["a==b","b!=c","c==a"]
输出:false

示例 5:

1
2
输入:["c==c","b==d","x!=z"]
输出:true

提示:

  1. 1 <= equations.length <= 500
  2. equations[i].length == 4
  3. equations[i][0]equations[i][3] 是小写字母
  4. equations[i][1] 要么是 '=',要么是 '!'
  5. equations[i][2]'='

由于相等关系具有传递性,所有相等的变量属于同一个集合,只关心连通性,不关心距离,因此很容易想到并查集。

算法流程:

  • 扫描所有等式,将等式两边的顶点进行合并;
  • 再扫描所有不等式,检查每一个不等式的两个顶点是不是在一个连通分量里:
    • 如果在,代表 a == b 和 a != b 同时成立,返回 false
    • 如果所有检查都没有矛盾,返回 true

使用一个数组 parent 存储每个变量的连通分量信息,其中的每个元素表示当前连通分量的父节点信息,如果父节点是自身,说明该变量为所在连通分量的根结点。一开始所有变量的父节点都是它们自身。对于合并操作,我们将第一个变量的根结点的父节点指向第二个变量的根结点;对于查找操作,我们沿着当前变量的父节点一路向上查找,直到找到根结点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class Solution {
public boolean equationsPossible(String[] equations) {
UnionFind unionFind = new UnionFind(26);

for(String equation : equations){
char[] charArray = equation.toCharArray();
if(charArray[1] == '='){
int index1 = charArray[0] - 'a';
int index2 = charArray[3] - 'a';
unionFind.union(index1, index2);
}
}

for(String equation : equations){
char[] charArray = equation.toCharArray();
if(charArray[1] == '!'){
int index1 = charArray[0] - 'a';
int index2 = charArray[3] - 'a';
if(unionFind.isConnected(index1, index2)){
//如果合并失败,表示等式有矛盾,返回false
return false;
}
}
}
//检查了所有不等式,没有发现矛盾,返回 true
return true;
}

private class UnionFind{
private int[] parent;

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

public int find(int x){
//找根结点
while(x != parent[x]){
//路径压缩
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}

public void union(int x, int y){
int rootX = find(x);
int rootY = find(y);
parent[rootX] = rootY;
}

public boolean isConnected(int x, int y){
//根结点相同则连通
return find(x) == find(y);
}
}

}

时间复杂度 O(n + ClogC),其中 n 是 equations 中的方程数量,C 是变量的总数,本题中变量都是小写字母即 C<=26。并查集代码中使用了路径压缩算法,对于每个方程的合并和查找的均摊时间复杂度都是 O(logC)。

空间复杂度 O(C),创建一个 parent 数组存储每个变量的连通分量信息。