Check If Two Strings Are Isomorphic
Problem and examples
Given two strings s and t, determine if they are isomorphic.
Two strings are isomorphic if the characters in s can be replaced to get t. The replacement must be consistent — every character in s must map to exactly one character in t, and no two characters from s can map to the same character in t.
The character mapping should be one-to-one and should preserve the order of characters. Return true if the strings are isomorphic, otherwise return false.
| s | t | Output | Description |
|---|---|---|---|
| "egg" | "add" | true | 'e' → 'a', 'g' → 'd' — consistent mapping |
| "foo" | "bar" | false | 'o' maps to two different characters, which breaks the rule |
| "paper" | "title" | true | 'p' → 't', 'a' → 'i', 'e' → 'l', 'r' → 'e' |
| "ab" | "aa" | false | 'a' → 'a' and 'b' → 'a' — two characters from 's' map to same char in 't' |
| "abc" | "def" | true | Each character maps uniquely and consistently |
| "" | "" | true | Both strings are empty — trivially isomorphic |
| "" | "abc" | false | One string is empty, the other is not |
| "abc" | "" | false | One string is empty, the other is not |
| "abca" | "zbxz" | true | 'a' → 'z', 'b' → 'b', 'c' → 'x' — all mappings valid |
| "abca" | "zbxy" | false | 'a' maps to both 'z' and 'y' — mapping is inconsistent |
Explore Isomorphic Strings
Load an example or enter your own input. Use the player controls to follow each change.
Understanding the Problem
We are given two strings, s and t, and we need to determine whether they are isomorphic.
Two strings are isomorphic if characters in one string can be replaced to get the second string — with a consistent, one-to-one mapping.
This means:
- Every character in
smaps to exactly one character int. - No two characters from
smap to the same character int.
Think of this as a character-translation problem: we’re translating each character in s to a character in t. The translation must be consistent and unique.
Step-by-Step Solution with Example
Step 1: Take a simple example
Let’s consider the example: s = "egg" and t = "add"
We’ll walk through each character pair and see how the mappings work.
Step 2: Use two hash maps
We use two maps (dictionaries):
s_to_t: Maps characters fromstott_to_s: Maps characters fromttos
Step 3: Iterate through the strings
We loop through each character pair:
i = 0 → s[0] = 'e', t[0] = 'a'
→ 'e' is not in s_to_t → map 'e' → 'a'
→ 'a' is not in t_to_s → map 'a' → 'e'
i = 1 → s[1] = 'g', t[1] = 'd'
→ 'g' is not in s_to_t → map 'g' → 'd'
→ 'd' is not in t_to_s → map 'd' → 'g'
i = 2 → s[2] = 'g', t[2] = 'd'
→ 'g' is already mapped to 'd' → OK
→ 'd' is already mapped to 'g' → OK
All characters follow consistent one-to-one mapping. So, the strings are isomorphic.
Step 4: Check for mismatches
Let’s consider another example: s = "foo", t = "bar"
i = 0 → 'f' → 'b'
i = 1 → 'o' → 'a'
i = 2 → 'o' → 'r' → ❌ mismatch! 'o' was already mapped to 'a'
So we return false.
Edge Cases
- Empty Strings: If both
sandtare empty, they are trivially isomorphic — nothing to map, no conflict. - Different Lengths: If
s.length !== t.length, they can’t be isomorphic. Each character must have a match. - Repeated Characters: Repetition is fine as long as the mapping stays consistent. For example:
s = "paper",t = "title"→ Valid isomorphic pair.
Finally
To solve this problem:
- Understand the core rule — one-to-one and consistent character mapping.
- Use two dictionaries to track both directions of mapping.
- Validate that no character breaks the mapping rule during iteration.
This approach is simple, beginner-friendly, and ensures you check all logical mapping conditions.
- Initialize two HashMaps:
s_to_tandt_to_s. - Loop through each character in both strings.
- For each character pair
(sc, tc)fromsandt: - If
scis mapped ins_to_t, it must map totc. - If
tcis mapped int_to_s, it must map tosc. - If any mismatch is found, return
false. - After the loop, return
true.
Implementation and efficiency
C implementation
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
bool isIsomorphic(char* s, char* t) {
if (strlen(s) != strlen(t)) return false;
int sToT[256] = {0};
int tToS[256] = {0};
for (int i = 0; s[i]; i++) {
char sc = s[i], tc = t[i];
if (sToT[sc] == 0 && tToS[tc] == 0) {
sToT[sc] = tc;
tToS[tc] = sc;
} else if (sToT[sc] != tc || tToS[tc] != sc) {
return false;
}
}
return true;
}
int main() {
printf("%s\n", isIsomorphic("egg", "add") ? "true" : "false");
printf("%s\n", isIsomorphic("foo", "bar") ? "true" : "false");
return 0;
}C++ implementation
#include <iostream>
#include <unordered_map>
using namespace std;
bool isIsomorphic(string s, string t) {
if (s.length() != t.length()) return false;
unordered_map<char, char> sToT, tToS;
for (int i = 0; i < s.length(); i++) {
char sc = s[i], tc = t[i];
if (sToT.count(sc)) {
if (sToT[sc] != tc) return false;
} else {
sToT[sc] = tc;
}
if (tToS.count(tc)) {
if (tToS[tc] != sc) return false;
} else {
tToS[tc] = sc;
}
}
return true;
}
int main() {
cout << boolalpha << isIsomorphic("egg", "add") << endl;
cout << boolalpha << isIsomorphic("foo", "bar") << endl;
return 0;
}Python implementation
def is_isomorphic(s: str, t: str) -> bool:
if len(s) != len(t):
return False
s_to_t = {}
t_to_s = {}
for sc, tc in zip(s, t):
if sc in s_to_t:
if s_to_t[sc] != tc:
return False
else:
s_to_t[sc] = tc
if tc in t_to_s:
if t_to_s[tc] != sc:
return False
else:
t_to_s[tc] = sc
return True
print(is_isomorphic("egg", "add")) # True
print(is_isomorphic("foo", "bar")) # FalseJava implementation
import java.util.*;
public class IsomorphicStrings {
public static boolean isIsomorphic(String s, String t) {
if (s.length() != t.length()) return false;
Map<Character, Character> sToT = new HashMap<>();
Map<Character, Character> tToS = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
char sc = s.charAt(i);
char tc = t.charAt(i);
if (sToT.containsKey(sc)) {
if (sToT.get(sc) != tc) return false;
} else {
sToT.put(sc, tc);
}
if (tToS.containsKey(tc)) {
if (tToS.get(tc) != sc) return false;
} else {
tToS.put(tc, sc);
}
}
return true;
}
public static void main(String[] args) {
System.out.println(isIsomorphic("egg", "add"));
System.out.println(isIsomorphic("foo", "bar"));
}
}JavaScript implementation
function isIsomorphic(s, t) {
if (s.length !== t.length) return false;
const sToT = {};
const tToS = {};
for (let i = 0; i < s.length; i++) {
let sc = s[i], tc = t[i];
if (sToT[sc] !== undefined) {
if (sToT[sc] !== tc) return false;
} else {
sToT[sc] = tc;
}
if (tToS[tc] !== undefined) {
if (tToS[tc] !== sc) return false;
} else {
tToS[tc] = sc;
}
}
return true;
}
console.log(isIsomorphic("egg", "add"));
console.log(isIsomorphic("foo", "bar"));Go implementation
package main
import (
"fmt"
)
func isIsomorphic(s string, t string) bool {
if len(s) != len(t) {
return false
}
sToT := make(map[byte]byte)
tToS := make(map[byte]byte)
for i := 0; i < len(s); i++ {
sc, tc := s[i], t[i]
if val, ok := sToT[sc]; ok {
if val != tc {
return false
}
} else {
sToT[sc] = tc
}
if val, ok := tToS[tc]; ok {
if val != sc {
return false
}
} else {
tToS[tc] = sc
}
}
return true
}
func main() {
fmt.Println(isIsomorphic("egg", "add"))
fmt.Println(isIsomorphic("foo", "bar"))
}Rust implementation
use std::collections::HashMap;
fn is_isomorphic(s: &str, t: &str) -> bool {
if s.len() != t.len() {
return false;
}
let (mut s_to_t, mut t_to_s) = (HashMap::new(), HashMap::new());
for (sc, tc) in s.chars().zip(t.chars()) {
if let Some(&mapped) = s_to_t.get(&sc) {
if mapped != tc {
return false;
}
} else {
s_to_t.insert(sc, tc);
}
if let Some(&mapped) = t_to_s.get(&tc) {
if mapped != sc {
return false;
}
} else {
t_to_s.insert(tc, sc);
}
}
true
}
fn main() {
println!("{}", is_isomorphic("egg", "add"));
println!("{}", is_isomorphic("foo", "bar"));
}Kotlin implementation
fun isIsomorphic(s: String, t: String): Boolean {
if (s.length != t.length) return false
val sToT = mutableMapOf<Char, Char>()
val tToS = mutableMapOf<Char, Char>()
for (i in s.indices) {
val sc = s[i]
val tc = t[i]
if (sToT.containsKey(sc)) {
if (sToT[sc] != tc) return false
} else {
sToT[sc] = tc
}
if (tToS.containsKey(tc)) {
if (tToS[tc] != sc) return false
} else {
tToS[tc] = sc
}
}
return true
}
fun main() {
println(isIsomorphic("egg", "add"))
println(isIsomorphic("foo", "bar"))
}Swift implementation
func isIsomorphic(_ s: String, _ t: String) -> Bool {
if s.count != t.count { return false }
var sToT = [Character: Character]()
var tToS = [Character: Character]()
let sChars = Array(s), tChars = Array(t)
for i in 0..<s.count {
let sc = sChars[i]
let tc = tChars[i]
if let mapped = sToT[sc], mapped != tc {
return false
} else {
sToT[sc] = tc
}
if let mapped = tToS[tc], mapped != sc {
return false
} else {
tToS[tc] = sc
}
}
return true
}
print(isIsomorphic("egg", "add"))
print(isIsomorphic("foo", "bar"))TypeScript implementation
function isIsomorphic(s: string, t: string): boolean {
if (s.length !== t.length) return false;
const sToT: Record<string, string> = {};
const tToS: Record<string, string> = {};
for (let i = 0; i < s.length; i++) {
const sc = s[i], tc = t[i];
if (sToT[sc] !== undefined) {
if (sToT[sc] !== tc) return false;
} else {
sToT[sc] = tc;
}
if (tToS[tc] !== undefined) {
if (tToS[tc] !== sc) return false;
} else {
tToS[tc] = sc;
}
}
return true;
}
console.log(isIsomorphic("egg", "add"));
console.log(isIsomorphic("foo", "bar"));Time complexity
| Case | Time | Explanation |
|---|---|---|
| best case | O(n) | We traverse both strings once, checking and updating maps in constant time. |
| average case | O(n) | Each character mapping takes constant time; total work is proportional to string length. |
| worst case | O(n) | In the worst case, every character in both strings is unique and requires a mapping. |
Space complexity
O(n)
Two hash maps are used that at most store all unique characters of the strings.