Last Updated: March 21, 2026
At its core, this problem asks: can we create a one-to-one mapping (a bijection) between the characters of s and the characters of t?
The word "isomorphic" means "same structure." Two strings are isomorphic if they share the same character pattern. In "egg", the pattern is "ABB" (first character is unique, second and third are the same). In "add", the pattern is also "ABB". So they're isomorphic. But "foo" has pattern "ABB" while "bar" has pattern "ABC", so they're not.
The critical subtlety is that the mapping must be bijective, meaning it must work in both directions. If 'a' maps to 'b', then no other character in s can map to 'b'. Consider s = "badc" and t = "baba": 'b' maps to 'b', 'a' maps to 'a', but then 'd' tries to map to 'b', which is already taken by 'b'. That's a conflict. A single map from s to t wouldn't catch this if you only checked forward direction, so you need to enforce the reverse mapping too.
1 <= s.length <= 5 * 10^4 → With up to 50,000 characters, we need O(n) or O(n log n). An O(n^2) approach would mean up to 2.5 * 10^9 operations, which is borderline but likely too slow.t.length == s.length → The strings are always the same length. No need to handle different lengths.s and t consist of any valid ASCII character → There are at most 128 (or 256 for extended ASCII) distinct characters. This means hash map size is bounded by a constant.The simplest way to verify isomorphism is to check whether every occurrence of a character in s maps to the same character in t, and vice versa. In a true brute force spirit, imagine you don't use a hash map at all. Instead, for each character pair (s[i], t[i]), you scan through all previous positions to verify consistency. If s[i] appeared before at some position j, then t[i] must equal t[j]. And if t[i] appeared before at position j, then s[i] must equal s[j].
i from 0 to n-1.i, scan all previous indices j from 0 to i-1.s[j] == s[i], check that t[j] == t[i]. If not, return false.t[j] == t[i], check that s[j] == s[i]. If not, return false.For each character at position i, we scan all previous positions to check for mapping conflicts. A hash map can answer "what does this character map to?" in O(1), but instead we're re-scanning from scratch every time. What if we stored each character's mapping as we go?
The brute force repeats the same lookup over and over: "what does this character map to?" A hash map gives us that answer in O(1). But here's the subtle part that many people miss: one hash map isn't enough.
Consider s = "badc" and t = "baba". If we only map s to t, we get: b->b, a->a, d->b. The issue is that both 'b' and 'd' map to 'b', but a single forward map doesn't detect that unless we also check the reverse direction.
So we need two maps: one from s->t and one from t->s. At each position, we check both. If s[i] already has a mapping and it doesn't match t[i], that's a conflict. If t[i] already has a mapping and it doesn't match s[i], that's also a conflict. Only if both checks pass do we record the mapping and move on.
The two-map approach enforces a bijection. The forward map mapST ensures that each character in s always maps to the same character in t (no character in s maps to two different characters in t). The reverse map mapTS ensures that each character in t is only mapped to by one character in s (no two characters in s map to the same character in t). Together, these two constraints guarantee a one-to-one correspondence.
mapST (s character -> t character) and mapTS (t character -> s character).i from 0 to n-1.charS = s[i] and charT = t[i].charS is already in mapST and mapST[charS] != charT, return false.charT is already in mapTS and mapTS[charT] != charS, return false.mapST[charS] = charT and mapTS[charT] = charS.The two hash maps work great, but we're storing explicit character mappings when all we really need to know is whether two characters appear in the same pattern. What if instead of storing mappings, we just stored the last position where each character appeared?
Here's a different way to think about isomorphism: two strings are isomorphic if and only if every pair of characters at the same position share the same "history." More concretely, the last time s[i] appeared must be the same as the last time t[i] appeared.
Think of it like this. If you're at position 5, and s[5] = 'a', and the last time 'a' appeared in s was at position 2, then the character at t[5] must also have last appeared at position 2 in t. If 'a' last appeared at position 2 but the corresponding character in t last appeared at position 3, the patterns don't match.
This approach is elegant because it avoids explicit character-to-character mappings entirely. We just need two arrays tracking the last seen index for each character.
lastSeenS and lastSeenT.i from 0 to n-1.lastSeenS[s[i]] != lastSeenT[t[i]], the characters have different occurrence patterns, so return false.lastSeenS[s[i]] = i and lastSeenT[t[i]] = i.