Isomorphic Strings

Different approaches to solve Leetcode 205 in JavaScript

Isomorphic Strings

Photo by Mick Haupt on Unsplash

Isomorphic String is a classic problem that challenges us to determine whether two given strings have a one-to-one character mapping. In other words, we need to check if we can replace characters in one string with corresponding characters from the other string while preserving the order.

In this article, we’ll explore an effective solution to solve this problem. We’ll analyze the time and space complexity of our solution, enabling us to understand its efficiency and scalability.

Problem Statement:

Given two strings s and t, determine if they are isomorphic.

Two strings s and t are isomorphic if the characters in s can be replaced to get t.

All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character, but a character may map to itself.


Approach 1: Index Comparison

  1. We iterate through each character in the strings s and t and compare their corresponding indices using the indexOf method.

  2. If the indices of the current characters do not match, we return false, indicating that the strings are not isomorphic.

  3. If the loop completes without finding any inconsistencies, we return true, indicating that the strings are isomorphic.

// ES6 Arrow Function
const isIsomorphic = (s, t) => {
    if(s.length !== t.length) return false;

    for(let i = 0; i < s.length; i++) {
        if(s.indexOf(s[i]) !== t.indexOf(t[i])) {
            return false;
        }
    }

    return true;
}

Time Complexity: O(N²), where N is the length of the strings s and t.

For each character in s, the code uses the indexOf method to search for the next occurrence of the character in both s and t. The indexOf method has a time complexity of O(n) in the worst case, and this search is performed for each character in s.

Space Complexity: O(1)

It does not use any additional data structures that scale with the input size. It only uses a constant amount of space to store variables and temporary values during the execution of the function.


Approach 2: Character Mapping

  1. The code uses two Map objects, to store the mapping between characters in s and t.

  2. It iterates through each character in the strings and checks if both maps do not contain mappings for the current characters.

  3. If not, it adds the mapping between the characters to both maps.

  4. If the maps already contain mappings, it checks if the existing mappings match the current characters.

  5. If they don’t match, the strings are not isomorphic, and the function returns false.

  6. If the loop completes without finding any inconsistencies, the strings are considered isomorphic, and the function returns true.

// ES6 Arrow Function
const isIsomorphic = (s, t) => {
    if(s.length !== t.length) return false;

    const sMap = new Map();
    const tMap = new Map();

    for(let i = 0; i < s.length; i++) {
        const charS = s[i];
        const charT = t[i];

        if(!sMap.has(charS) && !tMap.has(charT)) {
            sMap.set(charS, charT);
            tMap.set(charT, charS);
        } else {
            if(sMap.get(charS) !== charT || tMap.get(charT) !== charS) {
                return false;
            }
        }
    }

    return true;
}

Time Complexity: O(N)

We are iterating through each character in the strings s and t once, performing constant-time operations within the loop. Therefore, the time complexity is linear.

Space Complexity: O(1)

The space complexity is determined by the size of the maps sMap and tMap. In the worst case, all characters in s and t are unique, resulting in a total of 26 entries in each map (assuming lowercase English alphabets). Therefore, the space complexity is O(1), as the maximum number of entries in the maps is fixed regardless of the input size.


Approach 3: Frequency Mapping

  1. We use two frequency objects sFreq and tFreq to keep track of the last seen index for each character in the strings s and t.

  2. The code iterates through the characters in the strings and checks if the last seen index of the current character in s matches the last seen index of the corresponding character in t.

  3. If they don’t match, the strings are not isomorphic, and the function returns false. Otherwise, it updates the frequency objects with the current index.

// ES6 Arrow Function
const isIsomorphic = (s, t) => {
    if(s.length !== t.length) return false;

    let sFreq = {}, tFreq = {};

    for(let i = 0; i < s.length; i++) {
        const charS = s[i];
        const charT = t[i];

        if(sFreq[charS] !== tFreq[charT]) return false;

        sFreq[charS] = i + 1;
        tFreq[charT] = i + 1;
    }

    return true;
}

Time Complexity: O(N)

We iterate through the characters of both input strings once. Therefore, the time complexity is linear.

Space Complexity: O(1)

The space complexity constant because the frequency objects sFreq and tFreq have a fixed size of at most 26 entries and are independent of the input size.


And there you have it guys! We’ve explored two different approaches, optimized our solutions, and hopefully had some fun along the way. I hope this article has provided you with valuable insights and helped you better understand the different approaches to solving this problem. Happy coding!

Problem - Leetcode 205