1153. String Transforms Into Another String π
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)
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)
wherea
is fromstr1
andb
is fromstr2
:- 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 charactera
needs to map to different characters at different positions, which is impossible (one-to-many mapping), so we returnFalse
- If character
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 EvaluatorExample 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:
Position | str1[i] | str2[i] | Action | Hash 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:
Position | str1[i] | str2[i] | Action | Hash 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:
Position | str1[i] | str2[i] | Action | Hash 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
takesO(n)
time - Converting
str2
to a set withset(str2)
requiresO(n)
time to iterate through all characters - The main loop with
zip(str1, str2)
iterates through both strings once, performingO(1)
dictionary operations for each character pair, resulting inO(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 mostC
unique character mappings (one for each distinct character instr1
) - The set created from
str2
can contain at mostC
unique characters, requiringO(C)
space - Since
C
is constant (26), the space complexity can also be consideredO(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.
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!