Isomorphic Strings LeetCode: The One-to-One Mapping Test
Isomorphic Strings (#205) looks deceptively simple: given two strings s and t, determine if the characters in s can be replaced to get t. But the real test is whether you understand one-to-one character mapping — a subtle hash map application that trips up candidates who reach for a single map and call it done.
The isomorphic strings LeetCode problem is the foundation for an entire family of structural equivalence questions. Word Pattern (#290), Group Shifted Strings (#249), and even anagram grouping all rely on the same bidirectional mapping insight. Master this problem and you unlock the pattern behind all of them.
In this walkthrough, you will learn why two hash maps are essential, walk through concrete examples, handle the edge cases that catch most people, and understand how this problem connects to the broader landscape of string pattern matching in coding interviews.
Understanding the Isomorphic Strings Problem
Two strings s and t are isomorphic if there is a one-to-one mapping from every character in s to a character in t such that replacing each character in s with its mapped character produces t. The mapping must preserve order — the first occurrence of a character in s always maps to the same character in t, and no two different characters in s can map to the same character in t.
Think of it as a cipher. If "e" maps to "a" in one position, it must map to "a" everywhere. And if "e" already claims "a", no other character in s is allowed to also map to "a". This is the one-to-one (bijective) constraint that makes the problem interesting.
Examples make it concrete. "egg" and "add" are isomorphic: e maps to a, g maps to d, and the structure e-g-g matches a-d-d perfectly. "foo" and "bar" are not isomorphic: f maps to b, then o maps to a, but the second o would need to map to r — conflicting with the first o mapping to a.
Pattern Family
Isomorphic Strings (#205) is the foundation for Word Pattern (#290) and Group Shifted Strings (#249) — all three test whether you can implement bidirectional character mapping.
Bidirectional Mapping — Why Two Hash Maps
The key insight is that you need TWO hash maps, not one. The first map tracks s[i] to t[i] — ensuring that each character in s consistently maps to the same character in t. The second map tracks t[i] to s[i] — ensuring that each character in t is claimed by only one character in s.
For each position i, check both maps. If s[i] is already in map1 and its mapped value does not equal t[i], the strings are not isomorphic. If t[i] is already in map2 and its mapped value does not equal s[i], the strings are not isomorphic. If neither conflicts, add both mappings and move to the next position.
This bidirectional check is what separates correct solutions from buggy ones. A single forward map (s to t) would accept "ab" and "aa" because a maps to a and b maps to a — no conflict in the forward direction. But in the reverse direction, "a" in t would need to map to both "a" and "b" in s, which violates the one-to-one constraint.
Implementation — O(n) Time, O(1) Space
The implementation is clean and direct. Create two hash maps (or arrays of size 128 for ASCII). Iterate through both strings simultaneously. At each position, check both maps for conflicts. If a conflict is found, return false immediately. If no conflict exists, record both mappings. After processing all characters, return true.
The time complexity is O(n) where n is the length of the strings — you visit each character exactly once. The space complexity is technically O(1) because the character set is fixed. With ASCII, you need at most 128 entries in each map. With lowercase English letters, you need at most 26 entries. The space does not grow with input size.
One subtle implementation detail: always check the length of both strings first. If they differ, the answer is immediately false. No valid one-to-one mapping can exist between strings of different lengths, and this early return saves you from index-out-of-bounds issues.
- 1Check if s.length !== t.length — if so, return false immediately
- 2Create two maps: mapST (s char -> t char) and mapTS (t char -> s char)
- 3For each index i from 0 to n-1, read s[i] and t[i]
- 4If mapST has s[i] and mapST[s[i]] !== t[i], return false
- 5If mapTS has t[i] and mapTS[t[i]] !== s[i], return false
- 6Set mapST[s[i]] = t[i] and mapTS[t[i]] = s[i]
- 7After the loop completes, return true
Two Maps, Not One
You need TWO maps, not one — s->t mapping ensures no two s-chars map to the same t-char, but you also need t->s mapping to ensure no two t-chars receive the same s-char.
Visual Walkthrough — "egg"/"add" and "foo"/"bar"
Walk through "egg" and "add" step by step. Position 0: s[0]="e", t[0]="a". Neither map has entries yet. Add e->a to mapST and a->e to mapTS. Position 1: s[1]="g", t[1]="d". Neither map has entries for g or d. Add g->d and d->g. Position 2: s[2]="g", t[2]="d". mapST already has g->d, which matches t[2]="d". mapTS already has d->g, which matches s[2]="g". No conflict. Return true.
Now walk through "foo" and "bar". Position 0: f->b and b->f added. Position 1: o->a and a->o added. Position 2: s[2]="o", t[2]="r". mapST already has o->a, but t[2] is "r" — conflict. The mapping says o should produce "a" but we need it to produce "r". Return false immediately.
The single-map trap is best shown with s="ab", t="aa". With only mapST: a->a (fine), b->a (no conflict in forward map — b has not been seen). Single map returns true. But with mapTS: a->a (fine at position 0), then at position 1, mapTS already has a->a, but s[1]="b" — conflict. Bidirectional mapping correctly returns false.
Edge Cases to Consider
Same string: if s and t are identical, they are always isomorphic. Every character maps to itself. This is the trivial case and your algorithm handles it naturally without special logic.
Single character: s="a", t="z" is isomorphic. Only one mapping exists and it cannot conflict with anything. Another trivial case that falls out of the general algorithm.
Different lengths: s="abc", t="ab" is immediately false. No one-to-one mapping can exist. Always check lengths first as an O(1) early return.
All same characters: s="aaa", t="bbb" is isomorphic (a->b consistently). But s="aaa", t="bba" is not — a maps to b at positions 0 and 1, then needs to map to a at position 2, creating a conflict in mapST.
- Same string (s == t): always true — identity mapping
- Single character: always true if lengths match
- Different lengths: always false — check first as O(1) guard
- All same chars in s: true only if all chars in t are also the same
- Full alphabet usage: works fine, maps just fill up to 26 entries
Single Map Trap
Using only one map (s->t) fails for cases like s="ab" t="aa" — both a and b would map to a, which should not be allowed. The reverse map catches this.
What Isomorphic Strings Teaches You
Isomorphic Strings teaches bidirectional mapping — the idea that a valid one-to-one relationship must be enforced in both directions. This is the same core insight behind Word Pattern (#290), where words map to characters and characters map back to words. If you can solve #205, you can solve #290 with minimal adaptation.
The pattern extends beyond strings. Group Shifted Strings (#249) uses a similar structural equivalence idea — two strings belong to the same group if one can be shifted to produce the other, which is essentially an isomorphic relationship between character differences. Anagram grouping also relies on finding structural equivalence, though through frequency counting rather than mapping.
The broader lesson is that many "easy" LeetCode problems encode fundamental algorithmic ideas. Isomorphic Strings looks like a simple string comparison problem, but it teaches a mapping technique you will use in medium and hard problems. Drill this pattern with YeetCode flashcards until the two-map approach becomes automatic — you will see it again.