Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Move from brute-force thinking to an efficient approach using union-find strategy.
You are given two strings of the same length s1 and s2 and a string baseStr.
We say s1[i] and s2[i] are equivalent characters.
s1 = "abc" and s2 = "cde", then we have 'a' == 'c', 'b' == 'd', and 'c' == 'e'.Equivalent characters follow the usual rules of any equivalence relation:
'a' == 'a'.'a' == 'b' implies 'b' == 'a'.'a' == 'b' and 'b' == 'c' implies 'a' == 'c'.For example, given the equivalency information from s1 = "abc" and s2 = "cde", "acd" and "aab" are equivalent strings of baseStr = "eed", and "aab" is the lexicographically smallest equivalent string of baseStr.
Return the lexicographically smallest equivalent string of baseStr by using the equivalency information from s1 and s2.
Example 1:
Input: s1 = "parker", s2 = "morris", baseStr = "parser" Output: "makkek" Explanation: Based on the equivalency information in s1 and s2, we can group their characters as [m,p], [a,o], [k,r,s], [e,i]. The characters in each group are equivalent and sorted in lexicographical order. So the answer is "makkek".
Example 2:
Input: s1 = "hello", s2 = "world", baseStr = "hold" Output: "hdld" Explanation: Based on the equivalency information in s1 and s2, we can group their characters as [h,w], [d,e,o], [l,r]. So only the second letter 'o' in baseStr is changed to 'd', the answer is "hdld".
Example 3:
Input: s1 = "leetcode", s2 = "programs", baseStr = "sourcecode" Output: "aauaaaaada" Explanation: We group the equivalent characters in s1 and s2 as [a,o,e,r,s,c], [l,p], [g,t] and [d,m], thus all letters in baseStr except 'u' and 'd' are transformed to 'a', the answer is "aauaaaaada".
Constraints:
1 <= s1.length, s2.length, baseStr <= 1000s1.length == s2.lengths1, s2, and baseStr consist of lowercase English letters.Problem summary: You are given two strings of the same length s1 and s2 and a string baseStr. We say s1[i] and s2[i] are equivalent characters. For example, if s1 = "abc" and s2 = "cde", then we have 'a' == 'c', 'b' == 'd', and 'c' == 'e'. Equivalent characters follow the usual rules of any equivalence relation: Reflexivity: 'a' == 'a'. Symmetry: 'a' == 'b' implies 'b' == 'a'. Transitivity: 'a' == 'b' and 'b' == 'c' implies 'a' == 'c'. For example, given the equivalency information from s1 = "abc" and s2 = "cde", "acd" and "aab" are equivalent strings of baseStr = "eed", and "aab" is the lexicographically smallest equivalent string of baseStr. Return the lexicographically smallest equivalent string of baseStr by using the equivalency information from s1 and s2.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Union-Find
"parker" "morris" "parser"
"hello" "world" "hold"
"leetcode" "programs" "sourcecode"
lexicographically-smallest-generated-string)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #1061: Lexicographically Smallest Equivalent String
class Solution {
private final int[] p = new int[26];
public String smallestEquivalentString(String s1, String s2, String baseStr) {
for (int i = 0; i < p.length; ++i) {
p[i] = i;
}
for (int i = 0; i < s1.length(); ++i) {
int x = s1.charAt(i) - 'a';
int y = s2.charAt(i) - 'a';
int px = find(x), py = find(y);
if (px < py) {
p[py] = px;
} else {
p[px] = py;
}
}
char[] s = baseStr.toCharArray();
for (int i = 0; i < s.length; ++i) {
s[i] = (char) ('a' + find(s[i] - 'a'));
}
return String.valueOf(s);
}
private int find(int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
}
}
// Accepted solution for LeetCode #1061: Lexicographically Smallest Equivalent String
func smallestEquivalentString(s1 string, s2 string, baseStr string) string {
p := make([]int, 26)
for i := 0; i < 26; i++ {
p[i] = i
}
var find func(int) int
find = func(x int) int {
if p[x] != x {
p[x] = find(p[x])
}
return p[x]
}
for i := 0; i < len(s1); i++ {
x := int(s1[i] - 'a')
y := int(s2[i] - 'a')
px := find(x)
py := find(y)
if px < py {
p[py] = px
} else {
p[px] = py
}
}
var s []byte
for i := 0; i < len(baseStr); i++ {
s = append(s, byte('a'+find(int(baseStr[i]-'a'))))
}
return string(s)
}
# Accepted solution for LeetCode #1061: Lexicographically Smallest Equivalent String
class Solution:
def smallestEquivalentString(self, s1: str, s2: str, baseStr: str) -> str:
def find(x: int) -> int:
if p[x] != x:
p[x] = find(p[x])
return p[x]
p = list(range(26))
for a, b in zip(s1, s2):
x, y = ord(a) - ord("a"), ord(b) - ord("a")
px, py = find(x), find(y)
if px < py:
p[py] = px
else:
p[px] = py
return "".join(chr(find(ord(c) - ord("a")) + ord("a")) for c in baseStr)
// Accepted solution for LeetCode #1061: Lexicographically Smallest Equivalent String
impl Solution {
pub fn smallest_equivalent_string(s1: String, s2: String, base_str: String) -> String {
fn find(x: usize, p: &mut Vec<usize>) -> usize {
if p[x] != x {
p[x] = find(p[x], p);
}
p[x]
}
let mut p = (0..26).collect::<Vec<_>>();
for (a, b) in s1.bytes().zip(s2.bytes()) {
let x = (a - b'a') as usize;
let y = (b - b'a') as usize;
let px = find(x, &mut p);
let py = find(y, &mut p);
if px < py {
p[py] = px;
} else {
p[px] = py;
}
}
base_str
.bytes()
.map(|c| (b'a' + find((c - b'a') as usize, &mut p) as u8) as char)
.collect()
}
}
// Accepted solution for LeetCode #1061: Lexicographically Smallest Equivalent String
function smallestEquivalentString(s1: string, s2: string, baseStr: string): string {
const p: number[] = Array.from({ length: 26 }, (_, i) => i);
const find = (x: number): number => {
if (p[x] !== x) {
p[x] = find(p[x]);
}
return p[x];
};
for (let i = 0; i < s1.length; i++) {
const x = s1.charCodeAt(i) - 'a'.charCodeAt(0);
const y = s2.charCodeAt(i) - 'a'.charCodeAt(0);
const px = find(x);
const py = find(y);
if (px < py) {
p[py] = px;
} else {
p[px] = py;
}
}
const s: string[] = [];
for (let i = 0; i < baseStr.length; i++) {
const c = baseStr.charCodeAt(i) - 'a'.charCodeAt(0);
s.push(String.fromCharCode('a'.charCodeAt(0) + find(c)));
}
return s.join('');
}
Use this to step through a reusable interview workflow for this problem.
Track components with a list or adjacency matrix. Each union operation may need to update all n elements’ component labels, giving O(n) per union. For n union operations total: O(n²). Find is O(1) with direct lookup, but union dominates.
With path compression and union by rank, each find/union operation takes O(α(n)) amortized time, where α is the inverse Ackermann function — effectively constant. Space is O(n) for the parent and rank arrays. For m operations on n elements: O(m × α(n)) total.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.