Facebook Pixel

1153. String Transforms Into Another String πŸ”’

HardGraphHash TableString
Leetcode Link

Problem Description

You are given two strings str1 and str2 of the same length. Your task is to determine whether you can transform str1 into str2 by performing zero or more conversions.

In each conversion, you can select one character in str1 and convert all occurrences of that character to any other lowercase English character. For example, if str1 = "aabcc", you could convert all 'a's to 'd', resulting in "ddbcc".

The key constraints are:

  • When you convert a character, you must convert all occurrences of that character at once
  • You can only convert to lowercase English letters
  • You can perform multiple conversions in sequence
  • Each character can only be mapped to one other character (you cannot map 'a' to both 'b' and 'c')

Return true if it's possible to transform str1 into str2 through such conversions, and false otherwise.

For example:

  • str1 = "aabcc", str2 = "ccdee" β†’ true (convert 'a'β†’'c', 'b'β†’'d', 'c'β†’'e')
  • str1 = "leetcode", str2 = "codeleet" β†’ false (impossible transformation)
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To understand if we can transform str1 into str2, let's think about what each conversion means. When we convert all occurrences of a character 'a' to 'b', we're essentially creating a mapping: 'a' β†’ 'b'. This mapping must be consistent - we cannot map 'a' to different characters.

The first observation is straightforward: if str1 and str2 are already equal, no conversion is needed, so we return true.

The critical insight comes from considering when a transformation is impossible. Imagine str2 contains all 26 lowercase letters. In this case, we need 26 distinct mappings to reach str2. However, if we're transforming characters, we might create conflicts. For instance, if we need to map both 'a' β†’ 'z' and 'b' β†’ 'z' (because different positions require it), we'd have two characters mapping to the same target, which could work. But the real problem arises when we need to use all 26 letters as targets - we have no "spare" letter to use as an intermediate step for chained conversions.

Think of it like a parking lot puzzle where you need one empty space to move cars around. If all 26 letter positions are occupied in str2, we have no room to maneuver our transformations without creating conflicts.

For the main checking logic, we need to ensure consistency: each character in str1 can only map to one character in str2. As we traverse both strings position by position, we record what each character in str1 should transform to. If we encounter the same character in str1 later but it needs to map to a different character in str2, the transformation is impossible.

This leads us to use a hash table to track mappings: for each character in str1, we store what it should transform to. If we see a contradiction (same source character needing to map to different targets), we return false.

Learn more about Graph patterns.

Solution Approach

Let's walk through the implementation step by step:

Step 1: Check if strings are already equal

if str1 == str2:
    return True

If the strings are identical, no transformation is needed.

Step 2: Check if all 26 letters are used in str2

if len(set(str2)) == 26:
    return False

We convert str2 to a set to count unique characters. If str2 contains all 26 lowercase letters, transformation is impossible because we have no "spare" letter to use as an intermediate step for conversions.

Step 3: Build and validate character mappings

d = {}
for a, b in zip(str1, str2):
    if a not in d:
        d[a] = b
    elif d[a] != b:
        return False

We use a hash table d to track the mapping from each character in str1 to its corresponding character in str2.

The algorithm works as follows:

  • We iterate through both strings simultaneously using zip(str1, str2)
  • For each pair of characters (a, b) where a is from str1 and b is from str2:
    • If character a hasn't been seen before (a not in d), we record its mapping: d[a] = b
    • If character a has been seen before, we check if its previous mapping matches the current requirement
    • If d[a] != b, it means character a needs to map to different characters at different positions, which is impossible (one-to-many mapping), so we return False

Step 4: Return success

return True

If we complete the iteration without finding any conflicts, the transformation is possible.

The time complexity is O(n) where n is the length of the strings, as we traverse them once. The space complexity is O(1) since the hash table can store at most 26 mappings (one for each lowercase letter).

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with str1 = "aabcc" and str2 = "ccdee".

Step 1: Check if strings are equal

  • "aabcc" == "ccdee" β†’ False, so we continue.

Step 2: Check if str2 uses all 26 letters

  • set(str2) = {'c', 'd', 'e'} β†’ 3 unique characters
  • Since 3 β‰  26, we have room to perform transformations, so we continue.

Step 3: Build character mappings

We'll iterate through both strings position by position:

Positionstr1[i]str2[i]ActionHash table d
0'a''c''a' not in d, add mapping{'a': 'c'}
1'a''c''a' in d, check: d['a']='c' matches 'c' βœ“{'a': 'c'}
2'b''d''b' not in d, add mapping{'a': 'c', 'b': 'd'}
3'c''e''c' not in d, add mapping{'a': 'c', 'b': 'd', 'c': 'e'}
4'c''e''c' in d, check: d['c']='e' matches 'e' βœ“{'a': 'c', 'b': 'd', 'c': 'e'}

Step 4: Return result

  • No conflicts were found during the mapping process
  • Return True

The transformation is possible with the mappings: 'a'β†’'c', 'b'β†’'d', 'c'β†’'e'.


Now let's see a failing example with str1 = "ab" and str2 = "aa":

Step 1: "ab" β‰  "aa", continue.

Step 2: set("aa") = {'a'}, only 1 unique character (not 26), continue.

Step 3: Build mappings:

Positionstr1[i]str2[i]ActionHash table d
0'a''a''a' not in d, add mapping{'a': 'a'}
1'b''a''b' not in d, add mapping{'a': 'a', 'b': 'a'}

Although we built the mappings without conflict, this would require both 'a' and 'b' to map to 'a'. While our hash table check passes (no one-to-many mapping), this represents a many-to-one mapping which is actually valid for this problem. The transformation would succeed, returning True.

Actually, let's consider str1 = "aa" and str2 = "ab" instead:

Positionstr1[i]str2[i]ActionHash table d
0'a''a''a' not in d, add mapping{'a': 'a'}
1'a''b''a' in d, check: d['a']='a' β‰  'b' βœ—Conflict!

Step 4: Return False due to conflict - 'a' cannot map to both 'a' and 'b'.

Solution Implementation

1class Solution:
2    def canConvert(self, str1: str, str2: str) -> bool:
3        # If both strings are identical, no conversion needed
4        if str1 == str2:
5            return True
6      
7        # If str2 uses all 26 letters, we cannot convert
8        # (we need at least one unused character as intermediate for conversion chains)
9        if len(set(str2)) == 26:
10            return False
11      
12        # Dictionary to store character mappings from str1 to str2
13        char_mapping = {}
14      
15        # Check if a consistent one-to-one mapping exists
16        for char1, char2 in zip(str1, str2):
17            if char1 not in char_mapping:
18                # Create new mapping for this character
19                char_mapping[char1] = char2
20            elif char_mapping[char1] != char2:
21                # Conflict: char1 already maps to a different character
22                return False
23      
24        # All characters have consistent mappings
25        return True
26
1class Solution {
2    public boolean canConvert(String str1, String str2) {
3        // If the strings are already equal, no conversion needed
4        if (str1.equals(str2)) {
5            return true;
6        }
7      
8        // Count unique characters in str2
9        int uniqueCharsInTarget = 0;
10        int[] targetCharCount = new int[26];
11        int stringLength = str1.length();
12      
13        // Count occurrences of each character in str2
14        for (int i = 0; i < stringLength; i++) {
15            int charIndex = str2.charAt(i) - 'a';
16            targetCharCount[charIndex]++;
17            // If this is the first occurrence of this character, increment unique count
18            if (targetCharCount[charIndex] == 1) {
19                uniqueCharsInTarget++;
20            }
21        }
22      
23        // If str2 uses all 26 letters, we cannot perform the transformation
24        // (we need at least one unused letter as a temporary placeholder for swapping)
25        if (uniqueCharsInTarget == 26) {
26            return false;
27        }
28      
29        // Build character mapping from str1 to str2
30        int[] charMapping = new int[26];  // Maps each character in str1 to a character in str2
31      
32        for (int i = 0; i < stringLength; i++) {
33            int sourceChar = str1.charAt(i) - 'a';
34            int targetChar = str2.charAt(i) - 'a';
35          
36            // If this source character hasn't been mapped yet, create the mapping
37            // (storing targetChar + 1 to distinguish from uninitialized 0)
38            if (charMapping[sourceChar] == 0) {
39                charMapping[sourceChar] = targetChar + 1;
40            } 
41            // If already mapped, check if it maps to the same target character
42            else if (charMapping[sourceChar] != targetChar + 1) {
43                // Same source character cannot map to different target characters
44                return false;
45            }
46        }
47      
48        return true;
49    }
50}
51
1class Solution {
2public:
3    bool canConvert(string str1, string str2) {
4        // If strings are identical, no conversion needed
5        if (str1 == str2) {
6            return true;
7        }
8      
9        // Count unique characters in str2
10        int charCount[26] = {0};
11        int uniqueCharsInStr2 = 0;
12      
13        for (char& ch : str2) {
14            if (++charCount[ch - 'a'] == 1) {
15                ++uniqueCharsInStr2;
16            }
17        }
18      
19        // If str2 uses all 26 letters, we cannot convert
20        // (no temporary character available for transformation chain)
21        if (uniqueCharsInStr2 == 26) {
22            return false;
23        }
24      
25        // Build mapping from str1 characters to str2 characters
26        // mapping[i] stores (target character + 1), 0 means unmapped
27        int mapping[26] = {0};
28      
29        for (int i = 0; i < str1.size(); ++i) {
30            int sourceChar = str1[i] - 'a';
31            int targetChar = str2[i] - 'a';
32          
33            if (mapping[sourceChar] == 0) {
34                // First time seeing this source character, create mapping
35                mapping[sourceChar] = targetChar + 1;
36            } else if (mapping[sourceChar] != targetChar + 1) {
37                // Source character already mapped to different target
38                // One-to-many mapping is not allowed
39                return false;
40            }
41        }
42      
43        return true;
44    }
45};
46
1/**
2 * Determines if string str1 can be converted to string str2 by replacing characters.
3 * Each character in str1 can only be mapped to one character in str2.
4 * 
5 * @param str1 - The source string to convert from
6 * @param str2 - The target string to convert to
7 * @returns true if str1 can be converted to str2, false otherwise
8 */
9function canConvert(str1: string, str2: string): boolean {
10    // If both strings are identical, no conversion needed
11    if (str1 === str2) {
12        return true;
13    }
14  
15    // If target string uses all 26 letters, conversion is impossible
16    // because we need at least one unused character as intermediate for transformation
17    if (new Set(str2).size === 26) {
18        return false;
19    }
20  
21    // Map to store character mappings from str1 to str2
22    const charMapping: Map<string, string> = new Map();
23  
24    // Iterate through each character in str1 with its index
25    for (const [index, char] of str1.split('').entries()) {
26        // If this character hasn't been mapped yet, create the mapping
27        if (!charMapping.has(char)) {
28            charMapping.set(char, str2[index]);
29        } 
30        // If character already mapped but to a different target character, conversion impossible
31        else if (charMapping.get(char) !== str2[index]) {
32            return false;
33        }
34    }
35  
36    // All mappings are consistent, conversion is possible
37    return true;
38}
39

Time and Space Complexity

The time complexity is O(n), where n is the length of the strings str1 and str2. This is because:

  • The initial string comparison str1 == str2 takes O(n) time
  • Converting str2 to a set with set(str2) requires O(n) time to iterate through all characters
  • The main loop with zip(str1, str2) iterates through both strings once, performing O(1) dictionary operations for each character pair, resulting in O(n) total time

The space complexity is O(C), where C is the size of the character set (in this problem, C = 26 for lowercase English letters). This is because:

  • The dictionary d can store at most C unique character mappings (one for each distinct character in str1)
  • The set created from str2 can contain at most C unique characters, requiring O(C) space
  • Since C is constant (26), the space complexity can also be considered O(1)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall: Misunderstanding the "all 26 letters" check

A common mistake is thinking that if str2 has all 26 letters, we can still perform the transformation by carefully ordering our conversions. However, this overlooks the fundamental issue of conversion cycles.

Why this matters: When str2 uses all 26 letters, every letter is "occupied" as a target. If we have a cycle in our mapping (like a→b, b→c, c→a), we need a temporary "parking spot" - an unused letter to break the cycle. Without any unused letters, breaking cycles becomes impossible.

Example of the pitfall:

# Incorrect approach - missing the 26-letter check
def canConvert(str1, str2):
    if str1 == str2:
        return True
  
    char_mapping = {}
    for char1, char2 in zip(str1, str2):
        if char1 not in char_mapping:
            char_mapping[char1] = char2
        elif char_mapping[char1] != char2:
            return False
  
    return True  # Wrong! Doesn't check if str2 has all 26 letters

Test case that breaks the incorrect solution:

  • str1 = "abcdefghijklmnopqrstuvwxyz"
  • str2 = "bcdefghijklmnopqrstuvwxyza"

This creates a cycle where each letter maps to the next one, with 'z' mapping back to 'a'. The incorrect solution would return True, but the transformation is actually impossible since we need an extra letter to break the cycle.

Solution: Always include the check for whether str2 contains all 26 unique characters:

if len(set(str2)) == 26:
    return False

This ensures we have at least one "spare" letter available for breaking conversion cycles when needed.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More