题目来源于leetcode,解法和思路仅代表个人观点。传送门。
难度:中等
题目
给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 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
思路
并查集
使用并查集的条件(特征)
- 结点之间的联系 具有 传递性, 所以 所有相同关系的结点 属于同一集合
- 结点之间的只关心连通性,不关心距离
- 并查集提供 两个操作【合并】和【查询】。
【合并】两个结点 所在的集合
【查询】两个结点 是否在同一个集合中
- 经过 【路径压缩】和【按秩合并】之后的并查集 【查找】的平均时间复杂度 能达到O(1)
- 如果只使用【路径压缩】,【查找】的平均时间复杂度是O(logN),其中N为顶点数
并查集的优化
路径压缩
隔代压缩
功能: 将结点x指向 【x的父节点的父节点】
只需要添加一行代码,虽然压缩不完全,但是多次压缩之后,也能达到【完全压缩】的效果
完全压缩
功能: 将x到根节点root(x->root)路径上的所有结点,都进行压缩
按“秩”合并
这里的 “秩” ,一般意义上 是 树的高度。
功能: 当两个结点合并的时候,将 树的高度 较小的(父)结点 指向 树高较大 的(父)结点。这样 合并的时候,树高不会发生变化。
本题解法
- 扫描所有式子,将 等式中的结点 两两构建并查集
- 扫描所有式子,将 不等式的结点 依次判断两个结点 是否 在同一个集合中
a) 如果 在同一个集合中,说明不等式 不成立,返回false
b) 如果 所有的不等式都成立,返回true
代码
class Solution {
public:
//并查集
class UnionFind{
public:
vector<int> parent;
//初始化
UnionFind(int n){
parent.resize(n);
//将每个结点 父节点的初始值 设置为自己
for(int i=0;i<n;i++){
parent[i] = i;
}
}
//查找x的父节点
int find(int x){
//如果这个结点不是孤立的,就循环
while(parent[x] != x){
//路径压缩(隔代压缩)
//因为根节点的parent[i] = i(等于他自己),所以可以这么写
parent[x] = parent[parent[x]];
//循环找到其父节点
x = parent[x];
}
//x的值 复用
return x;
}
//合并x,y所在的集合
void unionNode(int x,int y){
//找到两个结点的父节点,让其中的一个父节点 指向 另一个结点的父节点
//路径压缩 和 按"秩"合并 选一个就行了,这里就不用 优化了
int px = find(x);
int py = find(y);
//px -> py
parent[px] = py;
}
//判断x,y是否在同一个集合中,就是判断 两个结点的父节点是否相同
bool isConnected(int x,int y){
return find(x) == find(y);
}
};
bool equationsPossible(vector<string>& equations) {
UnionFind unionFind(26);
//扫描等式,合并等式们
for(string s:equations){
//如果是等式
if(s[1]=='='){
int index1 = s[0] - 'a';
int index2 = s[3] - 'a';
unionFind.unionNode(index1,index2);
}
}
//扫描不等式,判断是否在同一个集合中
for(string s:equations){
//如果是不等式
if(s[1] == '!'){
int index1 = s[0] - 'a';
int index2 = s[3] - 'a';
//如果两个结点 在 同一个 集合中,就表示 等式不满足
if(unionFind.isConnected(index1,index2)){
return false;
}
}
}
return true;
}
};
算法复杂度
时间复杂度: O(n+ClogC),其中 n 是 equations 中的方程数量,C是变量的总数,在本题中变量都是小写字母,即 C≤26。
上面的并查集代码中使用了路径压缩优化,对于每个方程的合并和查找的均摊时间复杂度都是 O(logC)。
由于需要遍历每个方程,因此总时间复杂度是 O(n+ClogC)。
空间复杂度: O(C)。创建一个数组 parent 存储每个变量的连通分量信息,由于变量都是小写字母,因此 parent 是长度为 C。