Lexicographically Smallest Equivalent String — Medium Problem & Solution
Two strings s1 and s2 of equal length declare letter equivalences: for every index i, s1[i] and s2[i] are equivalent.
- Difficulty: Medium
- Topics: Strings, Union Find
- Asked at: Amazon, Google
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
Two strings s1 and s2 of equal length declare letter equivalences: for every index i, s1[i] and s2[i] are equivalent.
These equivalences behave as you would expect: every letter is equivalent to itself, equivalence is symmetric, and it is transitive (if a ~ b and b ~ c, then a ~ c).
Using them, you may replace any letter of baseStr with any letter equivalent to it. Return the lexicographically smallest string you can obtain.
Example 1
Input: s1 = "kairo", s2 = "codek", baseStr = "rock"
Output: eaaa
Explanation: The classes are {a, c, k, o}, {d, i} and {e, r}; each letter becomes the smallest of its class.
Example 2
Input: s1 = "abc", s2 = "bcd", baseStr = "dxb"
Output: axa
Explanation: `x` is only equivalent to itself.
Constraints
1 <= s1.length, s2.length, baseStr.length <= 1000s1.length == s2.lengths1, s2 and baseStr consist of lowercase English letters
How to solve Lexicographically Smallest Equivalent String
Union-find on the alphabet: merge s1[i] with s2[i] for every index, keep the smallest letter as each set's root, then map each character of baseStr to its root.
Approach
- Initialise
root[c] = cfor the 26 letters. - For each index
i, find the roots ofs1[i]ands2[i]and attach the larger root under the smaller one. - Build the answer by replacing every character of
baseStrwithfind(character).
Why it works
The equivalence classes are exactly the connected components of the graph whose edges are the pairs (s1[i], s2[i]), which union-find computes. Characters of baseStr can be changed independently, so the lexicographically smallest result picks the minimum letter of each character's class at every position — and attaching the larger root under the smaller keeps that minimum at the root.
Complexity
- Time —
O((n + m) · α(26)) - Space —
O(26)
Pitfalls
- Union by smaller root, not by smaller letter of the pair — the pair's letters may not be their sets' roots.
- Letters that never appear in
s1/s2stay as they are. - Equivalence is transitive:
a ~ bandb ~ cmeansccan becomeaeven though they never appear together.
Reference solution
Python
def smallestEquivalentString(s1: str, s2: str, baseStr: str) -> str:
root = list(range(26))
def find(x):
while root[x] != x:
root[x] = root[root[x]]
x = root[x]
return x
for c1, c2 in zip(s1, s2):
r1, r2 = find(ord(c1) - 97), find(ord(c2) - 97)
if r1 < r2:
root[r2] = r1
elif r2 < r1:
root[r1] = r2
return "".join(chr(find(ord(c) - 97) + 97) for c in baseStr)JavaScript
var smallestEquivalentString = function(s1, s2, baseStr) {
var root = [];
for (var i = 0; i < 26; i++) root.push(i);
var find = function(x) {
while (root[x] !== x) { root[x] = root[root[x]]; x = root[x]; }
return x;
};
for (var k = 0; k < s1.length; k++) {
var r1 = find(s1.charCodeAt(k) - 97), r2 = find(s2.charCodeAt(k) - 97);
if (r1 < r2) root[r2] = r1;
else if (r2 < r1) root[r1] = r2;
}
var out = [];
for (var j = 0; j < baseStr.length; j++) out.push(String.fromCharCode(find(baseStr.charCodeAt(j) - 97) + 97));
return out.join("");
};Also on the editorial tab: C, C#, C++, Go, Java, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 424 strings problems · the whole catalogue
Learn the technique: Strings · Union-Find (Disjoint Set Union)