990 - Satisfiability of Equality Equations
Difficulty: Medium | Pattern: Union-Find | Company tags: Google, Amazon, Facebook
Problem Statement
You are given an array of strings equations that represent relationships between variables where each string equations[i] is of length 4 and takes one of two forms: "xi==yi" or "xi!=yi". Here, xi and yi are lowercase letters (not necessarily different).
Return true if it is possible to assign integers to variable names so as to satisfy all the given equations, or false otherwise.
Example 1:
Input: equations = ["a==b","b!=a"]
Output: false (a==b and b!=a is a contradiction)
Example 2:
Input: equations = ["b==a","a==b"]
Output: true (a and b can be the same value)
Approach: Union-Find — O(n α(n)), O(1)
Key insight:
- Process all
==equations first — union those variables. - Then check all
!=equations — if both variables have the same root, contradiction.
def equationsPossible(equations: list[str]) -> bool:
parent = list(range(26))
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]] # path compression
x = parent[x]
return x
def union(x, y):
parent[find(x)] = find(y)
# Pass 1: union all equal pairs
for eq in equations:
if eq[1] == '=':
union(ord(eq[0]) - ord('a'), ord(eq[3]) - ord('a'))
# Pass 2: check inequality pairs
for eq in equations:
if eq[1] == '!':
a, b = ord(eq[0]) - ord('a'), ord(eq[3]) - ord('a')
if find(a) == find(b):
return False
return True
Algorithm Flow
Dry Run
equations = ["a==b","b!=a"]
- Union 'a' and 'b' (eq[1]='=') → parent['a'] = 'b'
- Check 'b' != 'a' → find('b')='b', find('a')='b' → same root → return False ✓
Why Two Passes?
A single pass might see a!=b before processing a==c and b==c, missing the transitive equality. Processing all == first ensures the union-find reflects all equalities before inequality checks.
Complexity
- Time: O(n α(n)) ≈ O(n) amortized
- Space: O(1) (26-element array)
Key Terms
| Term | Definition |
|---|---|
| Union-Find (DSU) | Data structure tracking disjoint sets of connected variables, supporting find and union operations. |
| Path compression | Optimization in find that rewires nodes directly to a shallower ancestor, flattening future lookups. |
| Amortized inverse-Ackermann O(α(n)) | Near-constant time complexity per find/union call once path compression (and/or union by rank) is applied. |
| Two-pass processing | Applying all equality constraints before inequality checks so transitive equalities are fully resolved first. |
| Root/representative | The canonical element (find(x)) identifying which connected component x belongs to. |
FAQ
- Can this be solved without Union-Find? Yes — build a graph where
==edges connect variables, run BFS/DFS to find connected components, then check every!=pair doesn't share a component; this is O(n) but more code than DSU. - What if the input has no
!=equations? Every consistent assignment works (just union all==pairs); the function returnsTrueimmediately since pass 2 never triggers a contradiction. - How would this change if variables were uppercase and lowercase letters (52 symbols)? Size the
parentarray to 52 and map each character to an index accordingly; the algorithm logic is unchanged. - Why must all
==equations be processed before any!=check? An inequality likea!=bcould be a false contradiction if a later equality chain (e.g.a==c,c==b) would actually forcea==b; processing unions first captures all transitive equalities. - What about self-referential equations like
"a==a"or"a!=a"?"a==a"is a no-op union;"a!=a"is always a contradiction sincefind(a) == find(a)trivially, so the function correctly returnsFalse.
Quick Revision
- Model each equation as a union (
==) or a same-set rejection (!=) over 26 lowercase-letter variables. - Pass 1: union all variables connected by
==equations. - Pass 2: for every
!=equation, if both sides share a root, returnFalse. - Path compression in
findkeeps lookups near O(1) amortized. - Order matters: unions must be fully applied before inequality checks to capture transitive equality.
- No
union by rankneeded here since the domain is fixed at 26 elements — path compression alone suffices. - Time: O(n·α(26)) ≈ O(n); Space: O(1) for the fixed 26-slot parent array.
- Contrast with graph-based components (BFS/DFS) which solve the same problem without a DSU structure.
Related Problems
- 200 - Number of Islands — connected-components pattern solvable with Union-Find or BFS/DFS.
- Pattern match: "Redundant Connection" (LeetCode 684) — classic Union-Find cycle-detection problem (create if the file exists in this directory).
- Pattern match: "Accounts Merge" (LeetCode 721) — Union-Find over string keys instead of fixed-size alphabet indices.