LeetCode 990. 等式方程的可满足性
题目描述


题意分析
每个方程表示两个小写字母变量相等或不相等,判断能否给所有变量赋整数值,使全部方程同时成立。相等关系具有传递性,因此不能只逐条检查直接出现的相反方程。
解法:并查集合并连通块
核心思路
[!blue]
相等方程把变量连接成若干组,同一组中的变量都被强制取相同值。并查集保存这种分组:
parent记录父节点,find(x)沿父链接找到根,根相同就属于同一组。合并两个等式端点时,连接的是它们所在集合的根,整组变量随之合并。先处理全部
==,让直接或间接的相等关系都体现到最终分组中。再处理!=:若两端同根,等式要求它们相等,而当前不等式要求不同,必然矛盾,可以立即返回false。如果所有不等式都连接不同集合,就给每个集合分配一个不同整数。同组变量满足全部等式,异组变量满足所有不等式,因此这样的赋值一定存在,返回
true。这也说明只需要记录等式分组,不需要再对不等关系做合并或传递。查根时顺便缩短父链接,只会让节点更快到达原来的根,不改变它所属的集合。
解题步骤
- 为 26 个字母建立独立集合,令
parent[i] = i。- 第一遍只处理等式,读取两端变量并合并它们的根。
- 第二遍只处理不等式,若两个变量的根相同,立即返回
false。- 全部检查通过则返回
true。自相等无需额外操作,自不等会被同根判断直接排除。
代码实现
class Solution {
public boolean equationsPossible(String[] equations) {
int[] parent = new int[26];
for (int i = 0; i < 26; i++) {
parent[i] = i;
}
for (String eq : equations) {
// 先把全部相等约束合并,再统一验证不等式。
if (eq.charAt(1) == '=') {
int a = eq.charAt(0) - 'a';
int b = eq.charAt(3) - 'a';
union(parent, a, b);
}
}
for (String eq : equations) {
// 此时分组已经确定,同根的不等式就是矛盾。
if (eq.charAt(1) == '!') {
int a = eq.charAt(0) - 'a';
int b = eq.charAt(3) - 'a';
if (find(parent, a) == find(parent, b)) {
return false;
}
}
}
return true;
}
private int find(int[] parent, int x) {
if (parent[x] != x) {
parent[x] = find(parent, parent[x]);
}
return parent[x];
}
private void union(int[] parent, int a, int b) {
int pa = find(parent, a);
int pb = find(parent, b);
if (pa != pb) {
// 连接两棵树的根,才能合并完整集合。
parent[pa] = pb;
}
}
}
func equationsPossible(equations []string) bool {
parent := make([]int, 26)
for i := 0; i < 26; i++ {
parent[i] = i
}
find := func(x int) int {
for parent[x] != x {
parent[x] = parent[parent[x]]
x = parent[x]
}
return x
}
union := func(a int, b int) {
pa := find(a)
pb := find(b)
if pa != pb {
// 连接两棵树的根,才能合并完整集合。
parent[pa] = pb
}
}
for _, eq := range equations {
// 先把全部相等约束合并,再统一验证不等式。
if eq[1] == '=' {
a := int(eq[0] - 'a')
b := int(eq[3] - 'a')
union(a, b)
}
}
for _, eq := range equations {
// 此时分组已经确定,同根的不等式就是矛盾。
if eq[1] == '!' {
a := int(eq[0] - 'a')
b := int(eq[3] - 'a')
if find(a) == find(b) {
return false
}
}
}
return true
}
复杂度分析
设方程数量为
n。
- 时间复杂度:$O(n)$。变量固定只有 26 个,即使沿整条父链查根,长度也受常数限制;两遍扫描均为线性时间。
- 空间复杂度:$O(1)$。并查集数组长度固定为 26,Java 查根递归深度也受此限制。
关键点总结
[!green]
- 并查集记录哪些变量被强制相等,不需要计算它们具体取什么值。
- 必须等所有等式处理完,再用最终分组验证不等式。
- 同根不等式给出矛盾;不存在这种矛盾时,给各组不同整数便能构造可行赋值。
易错点总结
[!yellow]
- 一边合并一边检查不等式,会漏掉后续等式才建立的相等关系。
- 不等关系没有相同的传递规则,不能用它合并集合。
- 比较两端当前的
parent值不一定能判断同组,必须查到各自的根。- 操作符的后一个字符都是
=,区分等式和不等式要读取下标 1。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 721. 账户合并 | 中等 | 先把相等关系合并成等价类,本题还要检查每条不等关系是否落入同一分量。 |
| LCR 111. 除法求值 | 中等 | 两题都合并变量所在的并查集分量;本题只保存等价关系,该题在父指针上增加比率并在路径压缩时同步更新权重。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!